Composites
Artifact panel
A side panel for generated documents and code with preview and source views, versions, actions, and streaming state.
Starts when it scrolls into view
NORD FROST · DARK · RADIUS 6/10 · BASE 16PX · SHADOW
- Type
ArtifactPanel- Source
artifact.rs- Story
artifact- Demo height
- 504 px
- Events
- ArtifactPanelEvent
Code
Cut from the gallery story this page runs, so it stays true.
let tokens = cx.theme().semantic_tokens();
let status: SharedString = self
.last_artifact_event
.clone()
.unwrap_or_else(|| "Switch views or versions, run an action, or close the panel".into());
let version = self.artifact_version.min(ARTIFACT_VERSIONS.len() - 1);
let (version_id, source) = ARTIFACT_VERSIONS[version];
let artifact = Artifact::new("comparison", "Supplier comparison", if version + 1 == ARTIFACT_VERSIONS.len() {
StreamedContent::running(source.to_owned())
} else {
StreamedContent::done(source)
})
.kind(ArtifactKind::Markdown)
.versions(ARTIFACT_VERSIONS.iter().enumerate().map(|(index, (id, _))| {
ArtifactVersion::new(*id, format!("v{}", index + 1))
}))
.active_version(version_id);
let conversation = v_flex()
.size_full()
.gap(tokens.spacing.sm)
.p(tokens.spacing.md)
.child(
div()
.text_xs()
.text_color(cx.theme().muted_foreground)
.child("Conversation"),
)
.child(
TextView::markdown(
"artifact-conversation",
"**User:** Compare the three suppliers for next month.\n\n**Agent:** I drafted a comparison as a document on the right; v3 is still being written.",
)
.selectable(true),
)
.when(!self.artifact_open, |this| {
this.child(
Button::new("artifact-reopen")
.outline()
.small()
.text_label("Reopen artifact")
.on_click(cx.listener(|this, _, _, cx| {
this.artifact_open = true;
this.last_artifact_event = Some("Reopened".into());
cx.notify();
})),
)
});
let panel = ArtifactPanel::new("artifact-panel", &artifact)
.view(self.artifact_view)
.actions([
ArtifactAction::new("open", "Open in editor"),
ArtifactAction::new("export", "Export"),
])
.on_event(cx.listener(|this, event: &ArtifactPanelEvent, _, cx| {
match event {
ArtifactPanelEvent::Closed { .. } => this.artifact_open = false,
ArtifactPanelEvent::ViewSelected { view, .. } => {
this.artifact_view = *view;
}
ArtifactPanelEvent::VersionSelected { version_id, .. } => {
if let Some(index) = ARTIFACT_VERSIONS
.iter()
.position(|(id, _)| *id == version_id.as_ref())
{
this.artifact_version = index;
}
}
ArtifactPanelEvent::ActionActivated { .. } => {}
}
this.last_artifact_event = Some(format!("{event:?}").into());
cx.notify();
}));
v_flex()
.gap(tokens.spacing.md)
.child(
div()
.id("artifact-story-host")
.h(px(440.))
.w_full()
.border_1()
.border_color(cx.theme().border)
.rounded(tokens.radius.lg)
.overflow_hidden()
.child(
h_resizable("artifact-split")
.with_state(&self.artifact_split)
.child(
resizable_panel()
.size(px(220.))
.size_range(px(160.)..px(360.))
.child(conversation),
)
.child(
resizable_panel()
.visible(self.artifact_open)
.child(div().size_full().p(tokens.spacing.sm).child(panel)),
),
),
)
.child(
div()
.id("artifact-story-status")
.role(Role::Status)
.aria_label(status.clone())
.text_xs()
.text_color(cx.theme().muted_foreground)
.child(status),
)
Events and ownership
- Who owns the state
- ArtifactPanel renders state supplied by the application; it does not own durable work.
- What it emits
- Interactive intent is reported through the typed ArtifactPanelEvent contract and stable application IDs.
- What it means
- A side panel for generated documents and code with preview and source views, versions, actions, and streaming state.
- Overflow and motion
- Growing content remains reachable in a bounded vertical surface; reduced motion preserves a useful state.
Event types
- ArtifactPanelEvent
What the demo does and does not show
The demo above is the same Rust as the native component, compiled to WebAssembly and drawn on a WebGPU canvas — not a mock-up, a recording, or a re-implementation. What it paints is what the component paints: layout, colour from the live theme, and motion.
The live browser specimen requires WebGPU; the native component remains the authoritative runtime.
A canvas cannot stand in for the platform, so treat everything that goes through the operating system as unproven here: keyboard handling and shortcuts, screen reader semantics, text input and IME, the clipboard, file drops, and the frame rate you would get on your own GPU. Judge those in a native build. If your browser has no WebGPU, the frame says so rather than showing an empty box.