---
type: doc
title: Attachment
description: Displays a file or image attachment with media, metadata, state, and actions.
---

> Fulldev UI for Astro. The `@fulldev` registry in `components.json` points at `https://ui.full.dev/r/styles/{style}/{name}.json`; see [installation](/astro/docs/installation.md).

```astro title="src/components/examples/attachment/attachment-demo.astro"
---
import FileCodeIcon from "@lucide/astro/icons/file-code"
import XIcon from "@lucide/astro/icons/x"

import {
  Attachment,
  AttachmentAction,
  AttachmentActions,
  AttachmentContent,
  AttachmentDescription,
  AttachmentGroup,
  AttachmentMedia,
  AttachmentTitle,
} from "@/components/ui/attachment"
import { Spinner } from "@/components/ui/spinner"

const images = [
  {
    name: "workspace.png",
    meta: "PNG · 820 KB",
    src: "https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=900&auto=format&fit=crop&q=80",
    alt: "Workspace",
  },
  {
    name: "desk-reference.jpg",
    meta: "JPG · 1.1 MB",
    src: "https://images.unsplash.com/photo-1497215728101-856f4ea42174?w=900&auto=format&fit=crop&q=80",
    alt: "Desk",
  },
  {
    name: "office-reference.jpg",
    meta: "JPG · 940 KB",
    src: "https://images.unsplash.com/photo-1497366811353-6870744d04b2?w=900&auto=format&fit=crop&q=80",
    alt: "Office",
  },
]
---

<div class="mx-auto flex w-full max-w-sm flex-col gap-3 py-12">
  <AttachmentGroup>
    {images.map((image) => (
      <Attachment orientation="vertical">
        <AttachmentMedia variant="image">
          <img src={image.src} alt={image.alt} />
        </AttachmentMedia>
        <AttachmentContent>
          <AttachmentTitle>{image.name}</AttachmentTitle>
          <AttachmentDescription>{image.meta}</AttachmentDescription>
        </AttachmentContent>
      </Attachment>
    ))}
  </AttachmentGroup>
  <Attachment state="uploading" class="w-full">
    <AttachmentMedia>
      <Spinner />
    </AttachmentMedia>
    <AttachmentContent>
      <AttachmentTitle>sales-dashboard.pdf</AttachmentTitle>
      <AttachmentDescription>Uploading · 64%</AttachmentDescription>
    </AttachmentContent>
    <AttachmentActions>
      <AttachmentAction aria-label="Cancel upload">
        <XIcon />
      </AttachmentAction>
    </AttachmentActions>
  </Attachment>
  <Attachment class="w-full">
    <AttachmentMedia>
      <FileCodeIcon />
    </AttachmentMedia>
    <AttachmentContent>
      <AttachmentTitle>message-renderer.tsx</AttachmentTitle>
      <AttachmentDescription>TypeScript · 12 KB</AttachmentDescription>
    </AttachmentContent>
    <AttachmentActions>
      <AttachmentAction aria-label="Remove message-renderer.tsx">
        <XIcon />
      </AttachmentAction>
    </AttachmentActions>
  </Attachment>
</div>
```

## Installation

```bash
npx shadcn@latest add @fulldev/attachment
```

## Usage

```astro
---
import FileIcon from "@lucide/astro/icons/file"

import {
  Attachment,
  AttachmentContent,
  AttachmentDescription,
  AttachmentMedia,
  AttachmentTitle,
} from "@/components/ui/attachment"
---

<Attachment>
  <AttachmentMedia>
    <FileIcon />
  </AttachmentMedia>
  <AttachmentContent>
    <AttachmentTitle>report.pdf</AttachmentTitle>
    <AttachmentDescription>2.4 MB</AttachmentDescription>
  </AttachmentContent>
</Attachment>
```

Attachment is a presentation component. It does not select, upload, remove, or persist files.

## Image

Set `variant="image"` on `AttachmentMedia` and render an `<img>` inside it. Use `orientation="vertical"` to stack the media above the content.

```astro title="src/components/examples/attachment/attachment-image.astro"
---
import XIcon from "@lucide/astro/icons/x"

import {
  Attachment,
  AttachmentAction,
  AttachmentActions,
  AttachmentContent,
  AttachmentDescription,
  AttachmentGroup,
  AttachmentMedia,
  AttachmentTitle,
  AttachmentTrigger,
} from "@/components/ui/attachment"

const images = [
  {
    name: "workspace.png",
    meta: "PNG · 820 KB",
    src: "https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=900&auto=format&fit=crop&q=80",
    alt: "Workspace",
  },
  {
    name: "desk-reference.jpg",
    meta: "JPG · 1.1 MB",
    src: "https://images.unsplash.com/photo-1497215728101-856f4ea42174?w=900&auto=format&fit=crop&q=80",
    alt: "Desk",
  },
  {
    name: "office-reference.jpg",
    meta: "JPG · 940 KB",
    src: "https://images.unsplash.com/photo-1497366811353-6870744d04b2?w=900&auto=format&fit=crop&q=80",
    alt: "Office",
  },
]
---

<div class="mx-auto w-full max-w-sm py-12">
  <AttachmentGroup class="w-full">
    {images.map((image) => (
      <Attachment orientation="vertical">
        <AttachmentMedia variant="image">
          <img src={image.src} alt={image.alt} />
        </AttachmentMedia>
        <AttachmentContent>
          <AttachmentTitle>{image.name}</AttachmentTitle>
          <AttachmentDescription>{image.meta}</AttachmentDescription>
        </AttachmentContent>
        <AttachmentActions>
          <AttachmentAction aria-label={`Remove ${image.name}`}>
            <XIcon />
          </AttachmentAction>
        </AttachmentActions>
        <AttachmentTrigger
          as="a"
          href={image.src}
          target="_blank"
          rel="noreferrer"
          aria-label={`Open ${image.name}`}
        />
      </Attachment>
    ))}
  </AttachmentGroup>
</div>
```

## States

```astro title="src/components/examples/attachment/attachment-states.astro"
---
import CheckIcon from "@lucide/astro/icons/check"
import ClockIcon from "@lucide/astro/icons/clock"
import FileWarningIcon from "@lucide/astro/icons/file-exclamation-point"
import FileTextIcon from "@lucide/astro/icons/file-text"
import RefreshCwIcon from "@lucide/astro/icons/refresh-cw"
import XIcon from "@lucide/astro/icons/x"

import {
  Attachment,
  AttachmentAction,
  AttachmentActions,
  AttachmentContent,
  AttachmentDescription,
  AttachmentMedia,
  AttachmentTitle,
} from "@/components/ui/attachment"
import { Spinner } from "@/components/ui/spinner"
---

<div class="mx-auto flex w-full max-w-sm flex-col gap-2 py-12">
  <Attachment state="idle" class="w-full">
    <AttachmentMedia>
      <ClockIcon />
    </AttachmentMedia>
    <AttachmentContent>
      <AttachmentTitle>selected-file.pdf</AttachmentTitle>
      <AttachmentDescription>Ready to upload</AttachmentDescription>
    </AttachmentContent>
    <AttachmentActions>
      <AttachmentAction aria-label="Remove selected-file.pdf">
        <XIcon />
      </AttachmentAction>
    </AttachmentActions>
  </Attachment>
  <Attachment state="uploading" class="w-full">
    <AttachmentMedia>
      <Spinner />
    </AttachmentMedia>
    <AttachmentContent>
      <AttachmentTitle>design-system.zip</AttachmentTitle>
      <AttachmentDescription>Uploading · 64%</AttachmentDescription>
    </AttachmentContent>
    <AttachmentActions>
      <AttachmentAction aria-label="Cancel upload">
        <XIcon />
      </AttachmentAction>
    </AttachmentActions>
  </Attachment>
  <Attachment state="processing" class="w-full">
    <AttachmentMedia>
      <FileTextIcon />
    </AttachmentMedia>
    <AttachmentContent>
      <AttachmentTitle>market-research.pdf</AttachmentTitle>
      <AttachmentDescription>Processing document</AttachmentDescription>
    </AttachmentContent>
    <AttachmentActions>
      <AttachmentAction aria-label="Remove market-research.pdf">
        <XIcon />
      </AttachmentAction>
    </AttachmentActions>
  </Attachment>
  <Attachment state="error" class="w-full">
    <AttachmentMedia>
      <FileWarningIcon />
    </AttachmentMedia>
    <AttachmentContent>
      <AttachmentTitle>financial-model.xlsx</AttachmentTitle>
      <AttachmentDescription>Upload failed. Try again.</AttachmentDescription>
    </AttachmentContent>
    <AttachmentActions>
      <AttachmentAction aria-label="Retry upload">
        <RefreshCwIcon />
      </AttachmentAction>
      <AttachmentAction aria-label="Remove financial-model.xlsx">
        <XIcon />
      </AttachmentAction>
    </AttachmentActions>
  </Attachment>
  <Attachment state="done" class="w-full">
    <AttachmentMedia>
      <CheckIcon />
    </AttachmentMedia>
    <AttachmentContent>
      <AttachmentTitle>uploaded-report.pdf</AttachmentTitle>
      <AttachmentDescription>Uploaded · 1.8 MB</AttachmentDescription>
    </AttachmentContent>
    <AttachmentActions>
      <AttachmentAction aria-label="Remove uploaded-report.pdf">
        <XIcon />
      </AttachmentAction>
    </AttachmentActions>
  </Attachment>
</div>
```

## Sizes

```astro title="src/components/examples/attachment/attachment-sizes.astro"
---
import FileTextIcon from "@lucide/astro/icons/file-text"

import {
  Attachment,
  AttachmentContent,
  AttachmentDescription,
  AttachmentMedia,
  AttachmentTitle,
} from "@/components/ui/attachment"
---

<div class="mx-auto flex w-full max-w-sm flex-col gap-3 py-12">
  <Attachment size="default" class="w-full">
    <AttachmentMedia>
      <FileTextIcon />
    </AttachmentMedia>
    <AttachmentContent>
      <AttachmentTitle>Default attachment</AttachmentTitle>
      <AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>
    </AttachmentContent>
  </Attachment>
  <Attachment size="sm" class="w-full">
    <AttachmentMedia>
      <FileTextIcon />
    </AttachmentMedia>
    <AttachmentContent>
      <AttachmentTitle>Small attachment</AttachmentTitle>
      <AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>
    </AttachmentContent>
  </Attachment>
  <Attachment size="xs" class="w-full">
    <AttachmentMedia>
      <FileTextIcon />
    </AttachmentMedia>
    <AttachmentContent>
      <AttachmentTitle>Extra small attachment</AttachmentTitle>
    </AttachmentContent>
  </Attachment>
</div>
```

## Group

```astro title="src/components/examples/attachment/attachment-group.astro"
---
import FileCodeIcon from "@lucide/astro/icons/file-code"
import FileTextIcon from "@lucide/astro/icons/file-text"
import TableIcon from "@lucide/astro/icons/table"
import XIcon from "@lucide/astro/icons/x"

import {
  Attachment,
  AttachmentAction,
  AttachmentActions,
  AttachmentContent,
  AttachmentDescription,
  AttachmentGroup,
  AttachmentMedia,
  AttachmentTitle,
} from "@/components/ui/attachment"

type Item = {
  name: string
  meta: string
  icon?: typeof FileTextIcon
  src?: string
}

const items: Item[] = [
  { name: "briefing-notes.pdf", meta: "PDF · 1.4 MB", icon: FileTextIcon },
  {
    name: "workspace.png",
    meta: "PNG · 820 KB",
    src: "https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=900&auto=format&fit=crop&q=80",
  },
  { name: "customers.csv", meta: "CSV · 18 KB", icon: TableIcon },
  { name: "renderer.tsx", meta: "TSX · 12 KB", icon: FileCodeIcon },
]
---

<div class="mx-auto w-full max-w-sm py-12">
  <AttachmentGroup class="w-full">
    {items.map((item) => {
      const Icon = item.icon

      return (
        <Attachment class="w-64">
          {item.src ? (
            <AttachmentMedia variant="image">
              <img src={item.src} alt={item.name} />
            </AttachmentMedia>
          ) : Icon ? (
            <AttachmentMedia>
              <Icon />
            </AttachmentMedia>
          ) : null}
          <AttachmentContent>
            <AttachmentTitle>{item.name}</AttachmentTitle>
            <AttachmentDescription>{item.meta}</AttachmentDescription>
          </AttachmentContent>
          <AttachmentActions>
            <AttachmentAction aria-label={`Remove ${item.name}`}>
              <XIcon />
            </AttachmentAction>
          </AttachmentActions>
        </Attachment>
      )
    })}
  </AttachmentGroup>
</div>
```

## Trigger

Add an `AttachmentTrigger` to make the whole card open a link or dialog. It fills the card behind the actions, so the actions stay clickable.

```astro title="src/components/examples/attachment/attachment-trigger.astro"
---
import CopyIcon from "@lucide/astro/icons/copy"
import FileSearchIcon from "@lucide/astro/icons/file-search"
import XIcon from "@lucide/astro/icons/x"

import {
  Attachment,
  AttachmentAction,
  AttachmentActions,
  AttachmentContent,
  AttachmentDescription,
  AttachmentMedia,
  AttachmentTitle,
  AttachmentTrigger,
} from "@/components/ui/attachment"
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog"
---

<div class="mx-auto w-full max-w-sm py-12">
  <Dialog>
    <Attachment class="w-full">
      <AttachmentMedia>
        <FileSearchIcon />
      </AttachmentMedia>
      <AttachmentContent>
        <AttachmentTitle>research-summary.pdf</AttachmentTitle>
        <AttachmentDescription>Open preview dialog</AttachmentDescription>
      </AttachmentContent>
      <AttachmentActions>
        <AttachmentAction aria-label="Copy link">
          <CopyIcon />
        </AttachmentAction>
        <AttachmentAction aria-label="Remove research-summary.pdf">
          <XIcon />
        </AttachmentAction>
      </AttachmentActions>
      <AttachmentTrigger
        data-slot="dialog-trigger"
        aria-label="Preview research-summary.pdf"
      />
    </Attachment>
    <DialogContent class="sm:max-w-md">
      <DialogHeader>
        <DialogTitle>research-summary.pdf</DialogTitle>
        <DialogDescription>
          The attachment trigger fills the card and opens the dialog, while the
          actions stay independently clickable above it.
        </DialogDescription>
      </DialogHeader>
    </DialogContent>
  </Dialog>
</div>
```

## API Reference

### Attachment

| Prop          | Type                                                         | Default        | Description                     |
| ------------- | ------------------------------------------------------------ | -------------- | ------------------------------- |
| `state`       | `"idle" \| "uploading" \| "processing" \| "error" \| "done"` | `"done"`       | Visual file state.              |
| `size`        | `"xs" \| "sm" \| "default"`                                  | `"default"`    | Controls spacing and type size. |
| `orientation` | `"horizontal" \| "vertical"`                                 | `"horizontal"` | Controls card layout.           |

`AttachmentMedia` accepts `variant="icon" | "image"`. `AttachmentAction` uses the Button API and supports `as`. `AttachmentTrigger` is polymorphic and covers the card, so give it visible or screen-reader-only link text. All parts forward native attributes.

Source: [`src/components/ui/attachment`](https://github.com/fulldotdev/ui/tree/main/src/components/ui/attachment)

## Install what this page shows

```bash
npx shadcn@latest add @fulldev/attachment-examples
```
