For the complete documentation index, see llms.txt. Markdown variants are available by appending .md to any URL or sending an Accept: text/markdown header. An agent skill is available at /.well-known/agent-skills/site-skill.md.
Home

Attachment

A file or image attachment card with media, metadata, upload states, and actions.

report.pdfPDF · 2.4 MB
uploading.zipUploading… 34%
failed.docxUpload failed
default.pdfPDF · 1.2 MB
small.pdfPDF · 1.2 MB
xs.pdfPDF
photo.jpgJPG · 3.8 MB
screenshot.pngPNG · 512 KB
data.csvReady
summary.pdfReady
notes.txtReady
archive.zipReady
import { Spinner } from "@/components/ui/spinner";
import { FileTextIcon, ImageIcon, Trash2Icon, XIcon } from "lucide-react";
import { Attachment, AttachmentAction, AttachmentActions, AttachmentContent, AttachmentDescription, AttachmentGroup, AttachmentMedia, AttachmentTitle, AttachmentTrigger } from "@/components/ui/attachment";

export const AttachmentDemo = () => (
    <div className="flex flex-col gap-6 max-w-xl">
      
      {/* States */}
      <div className="flex flex-wrap gap-3">
        {/* Done */}
        <Attachment state="done">
          <AttachmentMedia><FileTextIcon /></AttachmentMedia>
          <AttachmentContent>
            <AttachmentTitle>report.pdf</AttachmentTitle>
            <AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>
          </AttachmentContent>
          <AttachmentActions><AttachmentAction aria-label="Remove"><XIcon /></AttachmentAction></AttachmentActions>
          <AttachmentTrigger />
        </Attachment>
        {/* Uploading */}
        <Attachment state="uploading">
          <AttachmentMedia><Spinner size="sm" /></AttachmentMedia>
          <AttachmentContent>
            <AttachmentTitle>uploading.zip</AttachmentTitle>
            <AttachmentDescription>Uploading… 34%</AttachmentDescription>
          </AttachmentContent>
          <AttachmentActions><AttachmentAction aria-label="Cancel"><XIcon /></AttachmentAction></AttachmentActions>
        </Attachment>
        {/* Error */}
        <Attachment state="error">
          <AttachmentMedia><FileTextIcon /></AttachmentMedia>
          <AttachmentContent>
            <AttachmentTitle>failed.docx</AttachmentTitle>
            <AttachmentDescription>Upload failed</AttachmentDescription>
          </AttachmentContent>
          <AttachmentActions><AttachmentAction variant="destructive" aria-label="Remove"><Trash2Icon /></AttachmentAction></AttachmentActions>
        </Attachment>
      </div>

      {/* Sizes */}
      <div className="flex flex-wrap items-center gap-3">
        <Attachment size="default">
          <AttachmentMedia>
            <FileTextIcon />
          </AttachmentMedia>
          <AttachmentContent>
            <AttachmentTitle>default.pdf</AttachmentTitle>
            <AttachmentDescription>PDF · 1.2 MB</AttachmentDescription>
          </AttachmentContent>
        </Attachment>
        <Attachment size="sm">
          <AttachmentMedia>
            <FileTextIcon />
          </AttachmentMedia>
          <AttachmentContent>
            <AttachmentTitle>small.pdf</AttachmentTitle>
            <AttachmentDescription>PDF · 1.2 MB</AttachmentDescription>
          </AttachmentContent>
        </Attachment>
        <Attachment size="xs">
          <AttachmentMedia>
            <FileTextIcon />
          </AttachmentMedia>
          <AttachmentContent>
            <AttachmentTitle>xs.pdf</AttachmentTitle>
            <AttachmentDescription>PDF</AttachmentDescription>
          </AttachmentContent>
        </Attachment>
      </div>

      {/* Image variant vertical */}
      <div className="flex flex-wrap gap-3">
        <Attachment orientation="vertical">
          <AttachmentMedia variant="image">
            <ImageIcon className="size-6 text-muted-foreground" />
          </AttachmentMedia>
          <AttachmentContent>
            <AttachmentTitle>photo.jpg</AttachmentTitle>
            <AttachmentDescription>JPG · 3.8 MB</AttachmentDescription>
          </AttachmentContent>
        </Attachment>
        <Attachment orientation="vertical">
          <AttachmentMedia variant="image">
            <ImageIcon className="size-6 text-muted-foreground" />
          </AttachmentMedia>
          <AttachmentContent>
            <AttachmentTitle>screenshot.png</AttachmentTitle>
            <AttachmentDescription>PNG · 512 KB</AttachmentDescription>
          </AttachmentContent>
        </Attachment>
      </div>

      {/* Group */}
      <AttachmentGroup>
        {["data.csv", "summary.pdf", "notes.txt", "archive.zip"].map((name) => (
          <Attachment key={name} size="sm">
            <AttachmentMedia>
              <FileTextIcon />
            </AttachmentMedia>
            <AttachmentContent>
              <AttachmentTitle>{name}</AttachmentTitle>
              <AttachmentDescription>Ready</AttachmentDescription>
            </AttachmentContent>
          </Attachment>
        ))}
      </AttachmentGroup>
    </div>
  );

States

Use state to reflect the upload lifecycle. uploading and processing shimmer the title; error applies destructive colours.

<Attachment state="idle">…</Attachment>
<Attachment state="uploading">…</Attachment>
<Attachment state="processing">…</Attachment>
<Attachment state="error">…</Attachment>
<Attachment state="done">…</Attachment>  {/* default */}

Sizes

<Attachment size="default">…</Attachment>
<Attachment size="sm">…</Attachment>
<Attachment size="xs">…</Attachment>

Orientation

<Attachment orientation="horizontal">…</Attachment>  {/* default */}
<Attachment orientation="vertical">…</Attachment>

Image media

Set variant="image" on AttachmentMedia to apply image-fitting styles.

<AttachmentMedia variant="image">
  <img src="/photo.jpg" alt="photo" />
</AttachmentMedia>

Clickable card

AttachmentTrigger renders an absolute overlay button/link over the card. Use asChild for a link.

<Attachment>
  <AttachmentMedia><FileTextIcon /></AttachmentMedia>
  <AttachmentContent>
    <AttachmentTitle>report.pdf</AttachmentTitle>
    <AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>
  </AttachmentContent>
  <AttachmentActions>
    <AttachmentAction aria-label="Remove"><XIcon /></AttachmentAction>
  </AttachmentActions>
  <AttachmentTrigger asChild>
    <a href="/files/report.pdf" />
  </AttachmentTrigger>
</Attachment>

Group

AttachmentGroup wraps multiple attachments in a horizontally scrollable snap row.

<AttachmentGroup>
  <Attachment>…</Attachment>
  <Attachment>…</Attachment>
</AttachmentGroup>

Props

Attachment

PropTypeDefaultDescription
state"idle" | "uploading" | "processing" | "error" | "done""done"Upload lifecycle state
size"default" | "sm" | "xs""default"Card density
orientation"horizontal" | "vertical""horizontal"Layout direction

AttachmentMedia

PropTypeDefaultDescription
variant"icon" | "image""icon"Icon or image treatment

AttachmentAction

PropTypeDefaultDescription
variantButton variant"ghost"Button style
sizeButton size"icon-xs"Button size

AttachmentTrigger

PropTypeDefaultDescription
asChildbooleanfalseRender as child element (e.g. <a>)

Usage

import {
  Attachment,
  AttachmentAction,
  AttachmentActions,
  AttachmentContent,
  AttachmentDescription,
  AttachmentMedia,
  AttachmentTitle,
  AttachmentTrigger,
} from "@/components/ui/attachment";
 
<Attachment state="done">
  <AttachmentMedia><FileTextIcon /></AttachmentMedia>
  <AttachmentContent>
    <AttachmentTitle>report.pdf</AttachmentTitle>
    <AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>
  </AttachmentContent>
</Attachment>

Specs

# Mint — Attachment Specs
 
## Component
- name: attachment
- file: registry/mint/attachment.tsx
- installs to: components/ui/attachment.tsx
- dependencies: radix-ui, class-variance-authority
- registryDependencies: button
 
## What is it
A card that represents a single file or image attachment throughout its upload lifecycle.
Use it to display file metadata (name, size, type), a thumbnail or icon, and contextual actions such as remove or retry.
Prefer Attachment over a plain list item when the file's upload state needs to be communicated visually; do not use it as a generic media card unrelated to file handling.
 
## Visual identity
Rounded card (rounded-xl) with a subtle border on --card background.
A muted square media slot sits alongside concise metadata text.
Dashed border signals idle/pending state; destructive tint signals error — both changes are driven by data-state without layout shift.
 
## Design tokens
--card: oklch(1 0 0) / oklch(0.205 0 0)                              — card surface
--card-foreground: oklch(0.145 0 0) / oklch(0.985 0 0)               — default text on card
--muted: oklch(0.96 0.02 155) / oklch(0.2 0.03 155)                  — media slot background; hover tint
--muted-foreground: oklch(0.52 0.06 155) / oklch(0.6 0.06 155)       — description text
--destructive: oklch(0.55 0.25 15) / oklch(0.704 0.191 22.216)       — error border, media tint, description text
--border: oklch(0.88 0.04 155 / 60%) / oklch(0.32 0.05 155 / 40%)    — card border (solid done/error; dashed idle)
--ring: oklch(0.5 0.14 155) / oklch(0.62 0.15 155)                   — focus-within ring (ring/50 opacity)
 
## Sub-components
- Attachment            → root card div; drives all group-data-* context via data-state, data-size, data-orientation
- AttachmentMedia       → square icon or image slot; destructive tint on error state
- AttachmentContent     → flex text column; min-w-0 to allow truncation
- AttachmentTitle       → truncated filename; shimmer animation on uploading and processing states
- AttachmentDescription → secondary metadata (type, size); destructive/80 on error state
- AttachmentActions     → action button row; positioned absolute top-right in vertical orientation
- AttachmentAction      → ghost icon-xs Button by default; accepts any Button variant/size
- AttachmentTrigger     → invisible absolute overlay (inset-0 z-10) making the whole card clickable; supports asChild for links
- AttachmentGroup       → horizontally scrollable snap row with scroll-fade-x for multiple attachments
 
## Variants
 
### Attachment — size
- default  → standard density; 10×10 media slot, text-sm
- sm       → compact; 8×8 media slot, text-xs
- xs       → extra-compact; 7×7 media slot, text-xs, rounded-lg
 
### Attachment — orientation
- horizontal → min-w-40 row layout; media left, content centre, actions right
- vertical   → w-24 column layout; media fills width, actions positioned absolute top-right
 
### AttachmentMedia — variant
- icon  → icon-only treatment; foreground colour inherits from card
- image → image-fitting treatment (object-cover); reduced opacity during non-terminal states, full opacity on done/idle
 
## States
- idle:       dashed border (border-dashed); signals awaiting upload
- uploading:  AttachmentTitle shimmer animation; media opacity reduced on image variant
- processing: AttachmentTitle shimmer animation; media opacity reduced on image variant
- error:      border-destructive/30; media bg-destructive/10 with destructive icon colour; description text-destructive/80
- done:       default solid border; full opacity; no shimmer
- focus-within: ring-1 ring-ring/50 on the root card when any child receives keyboard focus
- hover:      bg-muted/50 on root card when card contains a focusable trigger or button
 
## Conventions
- Always set a meaningful state prop to communicate upload progress to users
- AttachmentTrigger must be the last child so it sits above content in the stacking order without blocking action buttons (z-10 vs z-20)
- AttachmentAction buttons require aria-label when icon-only (default usage)
- Do not nest AttachmentTrigger and AttachmentAction without ensuring AttachmentActions has z-20 — the trigger sits at z-10
- Use AttachmentGroup for lists of attachments to get accessible horizontal scrolling with snap behaviour
- Sub-components expose data-slot attributes for CSS targeting: "attachment", "attachment-media", "attachment-content", "attachment-title", "attachment-description", "attachment-actions", "attachment-action", "attachment-trigger", "attachment-group"
- Pair with a Spinner (data-slot="spinner") inside AttachmentMedia during uploading/processing — vertical orientation auto-sizes the spinner to size-6

Installation

GIN Design Plugin
$ npx shadcn add https://gin-design.vercel.app/attachment