Attachment
A file chip for uploads, previews and link cards. Composes media, title and actions through upload states in horizontal or vertical orientation.
Playground
<Attachment>
<AttachmentMedia>
<FileTextIcon />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>sales-dashboard.pdf</AttachmentTitle>
<AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<AttachmentAction aria-label="Remove sales-dashboard.pdf">
<XIcon />
</AttachmentAction>
</AttachmentActions>
</Attachment>Preview
The default layout is a horizontal chip: an AttachmentMedia square, an AttachmentContent block with title and description, and trailing buttons in AttachmentActions.
<Attachment>
<AttachmentMedia>
<FileTextIcon />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>sales-dashboard.pdf</AttachmentTitle>
<AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<AttachmentAction aria-label="Remove sales-dashboard.pdf">
<XIcon />
</AttachmentAction>
</AttachmentActions>
</Attachment>Installation
npx shadcn@latest add https://espresso-base-ui.vercel.app/r/attachment.jsonUsage
import {
Attachment,
AttachmentAction,
AttachmentActions,
AttachmentContent,
AttachmentDescription,
AttachmentGroup,
AttachmentMedia,
AttachmentTitle,
AttachmentTrigger,
} from "@/components/ui/attachment"<Attachment>
<AttachmentMedia>
<FileTextIcon />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>report.pdf</AttachmentTitle>
<AttachmentDescription>PDF · 1.1 MB</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<AttachmentAction aria-label="Remove report.pdf">
<XIcon />
</AttachmentAction>
</AttachmentActions>
</Attachment>Media variants
AttachmentMedia takes a variant: icon (default), image for a thumbnail, video and audio for embedded media-chrome players, and link for a rich link-preview card.
<Attachment>
<AttachmentMedia variant="image">
<img src="/path/to/cover.png" alt="Cover" />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>cover-art.png</AttachmentTitle>
<AttachmentDescription>PNG · 840 KB</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<AttachmentAction aria-label="Download cover-art.png">
<DownloadIcon />
</AttachmentAction>
<AttachmentAction aria-label="Remove cover-art.png">
<XIcon />
</AttachmentAction>
</AttachmentActions>
</Attachment>States
state reflects the upload lifecycle: idle draws a dashed drop target, uploading and processing shimmer the title (pair them with a Spinner in the media slot), error tints the media and description red, and done (the default) is the resting card.
<Attachment state="uploading">
<AttachmentMedia>
<Spinner />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>report-q3.pdf</AttachmentTitle>
<AttachmentDescription>Uploading · 62%</AttachmentDescription>
</AttachmentContent>
</Attachment>
<Attachment state="error">
<AttachmentMedia>
<TriangleAlertIcon />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>archive.zip</AttachmentTitle>
<AttachmentDescription>Upload failed · retry</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<AttachmentAction aria-label="Remove archive.zip">
<XIcon />
</AttachmentAction>
</AttachmentActions>
</Attachment>Sizes and orientation
size scales the chip (xs, sm, default, plus a large lg card meant for vertical galleries), and orientation="vertical" stacks the media above the caption.
<Attachment size="sm">
<AttachmentMedia>
<FileTextIcon />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>proposal.docx</AttachmentTitle>
<AttachmentDescription>size = sm</AttachmentDescription>
</AttachmentContent>
</Attachment>
<Attachment orientation="vertical">
<AttachmentMedia>
<FileSpreadsheetIcon />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>budget.xlsx</AttachmentTitle>
<AttachmentDescription>XLSX · 96 KB</AttachmentDescription>
</AttachmentContent>
</Attachment>Clickable card
AttachmentTrigger overlays an invisible full-card button (or link, via render) so the whole chip is clickable while AttachmentActions stays independently interactive above it.
<Attachment>
<AttachmentMedia>
<FileTextIcon />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>press-release.pdf</AttachmentTitle>
<AttachmentDescription>Click to preview</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<AttachmentAction aria-label="Download press-release.pdf">
<DownloadIcon />
</AttachmentAction>
</AttachmentActions>
<AttachmentTrigger
render={<a href="#" aria-label="Preview press-release.pdf" />}
/>
</Attachment>API reference
Attachment is a compound component. The root sets state, size and orientation as data attributes, and every part styles itself off those via group selectors – so a single prop change on the root restyles the whole card. All parts accept className plus the standard props of the element they render.
Attachment
| Prop | Type | Default | Description |
|---|---|---|---|
| state | "idle" | "uploading" | "processing" | "error" | "done" | "done" | Upload lifecycle. idle draws a dashed border, uploading/processing shimmer the title, error tints media and description in destructive colors. |
| size | "xs" | "sm" | "default" | "lg" | "default" | Chip scale. lg is a fixed-footprint card (140 × 190) intended for vertical orientation; it hides actions and, with image media, goes full-bleed. |
| orientation | "horizontal" | "vertical" | "horizontal" | horizontal places media beside the caption; vertical stacks media on top and floats actions over the top-right corner. |
AttachmentMedia
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "icon" | "image" | "video" | "audio" | "link" | "icon" | What the media slot renders. icon centers an svg; image shows a cropped thumbnail; video/audio wrap a plain <video>/<audio> child in a media-chrome player; link renders a rich-preview banner. |
| Component | Description |
|---|---|
| AttachmentMedia | Leading media square (data-slot="attachment-media", data-variant reflects the variant prop). |
| AttachmentContent | Caption column that holds title and description (data-slot="attachment-content"). |
| AttachmentTitle | Truncated file name (data-slot="attachment-title"). Shimmers while the root state is uploading or processing. |
| AttachmentDescription | Muted meta line – type, size, progress (data-slot="attachment-description"). Turns destructive in the error state. |
| AttachmentActions | Trailing action cluster (data-slot="attachment-actions"). Kept above AttachmentTrigger so its buttons stay clickable. |
| AttachmentAction | A Button preset to variant="ghost" size="icon-xs" (data-slot="attachment-action"). Accepts all Button props. |
| AttachmentTrigger | Invisible full-card overlay button (data-slot="attachment-trigger"). Use render to swap in an <a> or router link. |
| AttachmentGroup | Horizontal, snap-scrolling row of attachments (data-slot="attachment-group"). With lg cards it fans them into a rotated pile instead. |
Accessibility & styling hooks
Give every AttachmentAction and AttachmentTrigger an aria-label that names the file, since their visible content is icon-only. The root exposes data-slot="attachment" along with data-state, data-size and data-orientation, and every part carries its own data-slot – target these from CSS for app-level overrides. Keyboard focus anywhere inside the card lights up a ring on the root via focus-within.