Attachment
A compound card presenting one attached file or image.
q3-board-review.pdf
2.4 MB · 18 pages
launch-hero@2x.png
6.1 MB · 68% uploaded
renewals-2026.csv
Checking 12,480 rows
contract-final.docx
Over the 25 MB limit
View code
<>
<Attachment.Group aria-label="Attachments on this message" mix={[maxIs("34rem")]}>
<Attachment.Trigger
commandfor="preview-attachment-dialog"
mix={[is("17rem"), attachmentTrigger()]}
>
<Attachment state="done">
<Attachment.Media>
<FileTextIcon aria-hidden="true" />
</Attachment.Media>
<Attachment.Content>
<Attachment.Title style={{ opacity: 1 }}>q3-board-review.pdf</Attachment.Title>
<Attachment.Description>2.4 MB · 18 pages</Attachment.Description>
</Attachment.Content>
<Attachment.Actions>
<Attachment.Action aria-label="Download q3-board-review.pdf">
<DownloadIcon />
</Attachment.Action>
</Attachment.Actions>
</Attachment>
</Attachment.Trigger>
<Attachment state="uploading" mix={[is("17rem")]}>
<Attachment.Media>
<ImageIcon aria-hidden="true" />
</Attachment.Media>
<Attachment.Content>
<Attachment.Title state="uploading" style={{ opacity: 1 }}>launch-hero@2x.png</Attachment.Title>
<Attachment.Description>6.1 MB · 68% uploaded</Attachment.Description>
</Attachment.Content>
</Attachment>
<Attachment state="processing" mix={[is("17rem")]}>
<Attachment.Media>
<FileSpreadsheetIcon aria-hidden="true" />
</Attachment.Media>
<Attachment.Content>
<Attachment.Title state="processing" style={{ opacity: 1 }}>renewals-2026.csv</Attachment.Title>
<Attachment.Description>Checking 12,480 rows</Attachment.Description>
</Attachment.Content>
</Attachment>
<Attachment state="error" mix={[is("17rem")]}>
<Attachment.Media>
<FileTextIcon aria-hidden="true" />
</Attachment.Media>
<Attachment.Content>
<Attachment.Title style={{ opacity: 1 }}>contract-final.docx</Attachment.Title>
<Attachment.Description>Over the 25 MB limit</Attachment.Description>
</Attachment.Content>
<Attachment.Actions>
<Attachment.Action aria-label="Retry contract-final.docx" color="danger" variant="ghost">
<RefreshCwIcon />
</Attachment.Action>
<Attachment.Action aria-label="Remove contract-final.docx" color="danger" variant="ghost">
<Trash2Icon />
</Attachment.Action>
</Attachment.Actions>
</Attachment>
</Attachment.Group>
<Dialog id="preview-attachment-dialog" aria-labelledby="preview-attachment-dialog-title">
<Dialog.Header>
<Dialog.Title id="preview-attachment-dialog-title">q3-board-review.pdf</Dialog.Title>
<Dialog.Description>
The card opened this. Pressing the download button inside it did not.
</Dialog.Description>
</Dialog.Header>
<Dialog.Close commandfor="preview-attachment-dialog" aria-label="Close" />
</Dialog>
</>Installation
An ordinary dependency: install it, import the theme once, and import the component where it is used.
npm add @sdxc/uiimport "@sdxc/ui/theme.css";Usage
import { Attachment } from "@sdxc/ui";The card's outer host, recolored to the danger tint at state="error" and
declaring the ui-attachment named container so nested parts reflow at the
card's width. A busy state sets aria-busy to the token ARIA recognizes.
Composition
The parts the component publishes, each a static property of the host.
AttachmentAttachment.MediaThe card's media well: a fixed-size, rounded box clipping the preview a consumer nests inside.Attachment.ContentThe card's text column, growing into whatever inline space Attachment.Media and Attachment.Actions leave it.Attachment.TitleThe file or image name in a native<p>, truncated to a single line with an ellipsis.Attachment.DescriptionThe card's supporting detail line: a muted, single-line<p>carrying a file size, a page count, or the explanation of what went wrong once the ancestor Attachment'sstatereads"error".Attachment.ActionsThe card's action row, shrink-proof so a narrow card's width is absorbed by Attachment.Content's own truncation while the actions keep their size.Attachment.ActionA single card action as a Button, defaulting to a compact,"ghost", neutral control so a row of actions reads as secondary to the card's title.Attachment.TriggerA wrapper<div>whosehreforcommandforstays an inert attribute until a consumer mixes inattachmentTrigger().Attachment.GroupA horizontally scroll-snapping row of cards, faded at both inline edges to hint at cards beyond the current view.
Examples
<Attachment state="error">
<Attachment.Media><FileIcon aria-hidden /></Attachment.Media>
<Attachment.Content>
<Attachment.Title>quarterly-report.pdf</Attachment.Title>
<Attachment.Description>{t("attachment.uploadFailed")}</Attachment.Description>
</Attachment.Content>
<Attachment.Actions>
<Attachment.Action aria-label={t("attachment.retry")}><RotateCwIcon /></Attachment.Action>
</Attachment.Actions>
</Attachment>Props
Read from the component's own types, so every prop, every default and every allowed value is listed.
| Prop | Type | Description |
|---|---|---|
state? | "idle" | "uploading" | "processing" | "error" | "done" | Lifecycle state of the file or image the card represents. Defaults to "idle". |
children | RemixNode | The card's compound parts: Attachment.Media, Attachment.Content, and Attachment.Actions. |
Also accepts everything in TagProps<"div">.
Attachment.Media
| Prop | Type | Description |
|---|---|---|
children | RemixNode | The preview itself: an <img>, or a decorative icon standing in for the file's type. |
Also accepts everything in TagProps<"div">.
Attachment.Content
| Prop | Type | Description |
|---|---|---|
children | RemixNode | Attachment.Title and, optionally, Attachment.Description. |
Also accepts everything in TagProps<"div">.
Attachment.Title
| Prop | Type | Description |
|---|---|---|
state? | Attachment.State | The ancestor Attachment's own state, repeated here because a component reads only its own props; passing the same value to both is what keeps them in sync. Left unset, the title renders plain. |
children | RemixNode | The file or image name. |
Also accepts everything in TagProps<"p">.
Attachment.Description
| Prop | Type | Description |
|---|---|---|
children | RemixNode | Supporting detail — a file size, a page count, an error message. |
Also accepts everything in TagProps<"p">.
Attachment.Actions
| Prop | Type | Description |
|---|---|---|
children | RemixNode | A run of Attachment.Action controls. |
Also accepts everything in TagProps<"div">.
Attachment.Action
Also accepts everything in Button.Props.
Attachment.Trigger
| Prop | Type | Description |
|---|---|---|
href? | string | Destination the whole card follows once attachmentTrigger() is mixed in, opened in place unless the activation asks for a new tab. It rides on a <div> so a nested Attachment.Action stays valid markup. |
commandfor? | string | id of a <dialog> element the whole card opens once attachmentTrigger() is mixed in, instead of following href. |
command? | "show-modal" | Invoker Commands verb dispatched to commandfor's dialog. Defaults to "show-modal". |
target? | string | Where href opens: a window/frame name, or "_blank" for a new tab. Meaningful only alongside href. |
children | RemixNode | The card's own markup — typically an Attachment. |
Also accepts everything in TagProps<"div">.
Attachment.Group
| Prop | Type | Description |
|---|---|---|
children | RemixNode | A run of Attachment or Attachment.Trigger cards. |
Also accepts everything in TagProps<"div">.
Types
| Name | Values | What it decides |
|---|---|---|
State | "idle" | "uploading" | "processing" | "error" | "done" | Lifecycle state of a card's file or image. |