Attachment

Attachment

A file chip for uploads, previews and link cards. Composes media, title and actions through upload states in horizontal or vertical orientation.

Playground

sales-dashboard.pdfPDF · 2.4 MB
title
description
state
orientation
removable
<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.

sales-dashboard.pdfPDF · 2.4 MB
<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.json

Usage

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.

Cover
cover-art.pngPNG · 840 KB
<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.

report-q3.pdfUploading · 62%
archive.zipUpload failed · retry
<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.

proposal.docxsize = sm
budget.xlsxXLSX · 96 KB
<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.

press-release.pdfClick to preview
<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

PropTypeDefaultDescription
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

PropTypeDefaultDescription
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.
ComponentDescription
AttachmentMediaLeading media square (data-slot="attachment-media", data-variant reflects the variant prop).
AttachmentContentCaption column that holds title and description (data-slot="attachment-content").
AttachmentTitleTruncated file name (data-slot="attachment-title"). Shimmers while the root state is uploading or processing.
AttachmentDescriptionMuted meta line – type, size, progress (data-slot="attachment-description"). Turns destructive in the error state.
AttachmentActionsTrailing action cluster (data-slot="attachment-actions"). Kept above AttachmentTrigger so its buttons stay clickable.
AttachmentActionA Button preset to variant="ghost" size="icon-xs" (data-slot="attachment-action"). Accepts all Button props.
AttachmentTriggerInvisible full-card overlay button (data-slot="attachment-trigger"). Use render to swap in an <a> or router link.
AttachmentGroupHorizontal, 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.

Search documentation

Search the component docs