Composites
Attachment previews
Composer and message attachments with thumbnails, kinds, upload state, and typed open or remove events.
Starts when it scrolls into view
NORD FROST · DARK · RADIUS 6/10 · BASE 16PX · SHADOW
- Type
AttachmentStrip- Source
attachment.rs- Story
attachments- Demo height
- 486 px
- Events
- AttachmentEvent
Code
Cut from the gallery story this page runs, so it stays true.
let tokens = cx.theme().semantic_tokens();
let composer_items = demo_attachments(&self.demo_thumbnail)
.into_iter()
.filter(|item| !self.removed_attachments.contains(item.id()))
.collect::<Vec<_>>();
let status: SharedString = self
.last_attachment_event
.clone()
.unwrap_or_else(|| "Remove a composer file or open a message file".into());
let caption = |text: &'static str| {
div()
.text_xs()
.text_color(cx.theme().muted_foreground)
.child(text)
};
v_flex()
.gap(tokens.spacing.md)
.child(caption("Composer: compact and removable"))
.child(
AttachmentStrip::new("composer-attachments")
.label("Prompt attachments")
.items(composer_items)
.removable(true)
.compact(true)
.on_event(cx.listener(|this, event: &AttachmentEvent, _, cx| {
if let AttachmentEvent::Removed { id } = event {
this.removed_attachments.insert(id.clone());
this.last_attachment_event =
Some(format!("Removed {id}").into());
cx.notify();
}
})),
)
.child(caption("Message: read-only, openable tiles"))
.child(
AttachmentStrip::new("message-attachments")
.label("Message attachments")
.items(demo_attachments(&self.demo_thumbnail))
.on_event(cx.listener(|this, event: &AttachmentEvent, _, cx| {
if let AttachmentEvent::Opened { id } = event {
this.last_attachment_event =
Some(format!("Opened {id}").into());
cx.notify();
}
})),
)
.child(caption("Upload lifecycle: queued, uploading, failed"))
.child(
AttachmentStrip::new("attachment-states")
.label("Upload states")
.items(demo_attachment_states()),
)
.child(
div()
.id("attachments-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
- AttachmentStrip renders state supplied by the application; it does not own durable work.
- What it emits
- Interactive intent is reported through the typed AttachmentEvent contract and stable application IDs.
- What it means
- Composer and message attachments with thumbnails, kinds, upload state, and typed open or remove events.
- Overflow and motion
- Growing content remains reachable in a bounded vertical surface; reduced motion preserves a useful state.
Event types
- AttachmentEvent
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.