Skip to content
SevenUI
Esc
navigateopen⌘Jpreview
Sign in
On this page

Attachment

Displays file attachments with media previews and actions.

"use client";

import { FileTextIcon } from "lucide-react";

import {
  Attachment,
  AttachmentContent,
  AttachmentDescription,
  AttachmentGroup,
  AttachmentMedia,
  AttachmentTitle,
} from "@/registry/base/ui/attachment";

export default function AttachmentDemo() {
  return (
    <AttachmentGroup className="w-full max-w-md">
      <Attachment>
        <AttachmentMedia>
          <FileTextIcon />
        </AttachmentMedia>
        <AttachmentContent>
          <AttachmentTitle>quarterly-report.pdf</AttachmentTitle>
          <AttachmentDescription>1.2 MB · PDF</AttachmentDescription>
        </AttachmentContent>
      </Attachment>
      <Attachment>
        <AttachmentMedia variant="image">
          <img src="/placeholder.svg" alt="" />
        </AttachmentMedia>
        <AttachmentContent>
          <AttachmentTitle>cover.png</AttachmentTitle>
          <AttachmentDescription>96 KB</AttachmentDescription>
        </AttachmentContent>
      </Attachment>
    </AttachmentGroup>
  );
}

Installation

npx shadcn@latest add https://sevenui.dev/r/attachment.json

Usage

import { FileTextIcon } from "lucide-react";

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

export function ReportAttachment() {
  return (
    <Attachment>
      <AttachmentMedia>
        <FileTextIcon />
      </AttachmentMedia>
      <AttachmentContent>
        <AttachmentTitle>quarterly-report.pdf</AttachmentTitle>
        <AttachmentDescription>1.2 MB · PDF</AttachmentDescription>
      </AttachmentContent>
    </Attachment>
  );
}

Built for chat composers, message threads, and upload lists. Wrap multiple attachments in AttachmentGroup for a snap-scrolling row. The state prop drives the upload lifecycle styling: idle renders a dashed border, uploading/processing shimmer the title, and error turns the media and description destructive.

Examples

Image cards

orientation="vertical" turns attachments into image-first cards — suited for photo pickers and gallery uploads.

"use client";

import {
  Attachment,
  AttachmentContent,
  AttachmentDescription,
  AttachmentGroup,
  AttachmentMedia,
  AttachmentTitle,
} from "@/registry/base/ui/attachment";

const images = [
  { name: "hero-light.png", size: "128 KB" },
  { name: "hero-dark.png", size: "132 KB" },
  { name: "og-image.png", size: "96 KB" },
];

export default function AttachmentImage() {
  return (
    <AttachmentGroup className="w-full max-w-md">
      {images.map((image) => (
        <Attachment key={image.name} orientation="vertical">
          <AttachmentMedia variant="image">
            <img src="/placeholder.svg" alt="" />
          </AttachmentMedia>
          <AttachmentContent>
            <AttachmentTitle>{image.name}</AttachmentTitle>
            <AttachmentDescription>{image.size}</AttachmentDescription>
          </AttachmentContent>
        </Attachment>
      ))}
    </AttachmentGroup>
  );
}

Actions and upload state

AttachmentActions holds buttons that stay clickable above the full-card AttachmentTrigger (the trigger sits behind them in stacking order). Icon-only actions and triggers need aria-labels.

"use client";

import { DownloadIcon, FileArchiveIcon, XIcon } from "lucide-react";

import {
  Attachment,
  AttachmentAction,
  AttachmentActions,
  AttachmentContent,
  AttachmentDescription,
  AttachmentMedia,
  AttachmentTitle,
  AttachmentTrigger,
} from "@/registry/base/ui/attachment";
import { Spinner } from "@/registry/base/ui/spinner";

export default function AttachmentActionsDemo() {
  return (
    <div className="flex w-full max-w-xs flex-col gap-3">
      <Attachment size="sm" className="w-full">
        <AttachmentMedia>
          <FileArchiveIcon />
        </AttachmentMedia>
        <AttachmentContent>
          <AttachmentTitle>design-assets.zip</AttachmentTitle>
          <AttachmentDescription>24 MB</AttachmentDescription>
        </AttachmentContent>
        <AttachmentActions>
          <AttachmentAction aria-label="Download design-assets.zip">
            <DownloadIcon />
          </AttachmentAction>
          <AttachmentAction aria-label="Remove design-assets.zip">
            <XIcon />
          </AttachmentAction>
        </AttachmentActions>
        <AttachmentTrigger aria-label="Preview design-assets.zip" />
      </Attachment>
      <Attachment size="xs" state="uploading" className="w-full">
        <AttachmentMedia>
          <Spinner />
        </AttachmentMedia>
        <AttachmentContent>
          <AttachmentTitle>screen-recording.mp4</AttachmentTitle>
          <AttachmentDescription>Uploading…</AttachmentDescription>
        </AttachmentContent>
      </Attachment>
    </div>
  );
}

API reference

Attachment

Extends div.

Prop Type Default
size "default" | "sm" | "xs" "default"
orientation "horizontal" | "vertical" "horizontal"
state "idle" | "uploading" | "processing" | "error" | "done" "done"

AttachmentMedia

Extends div.

Prop Type Default
variant "icon" | "image" "icon"

image expects an img child and dims it while uploading; icon sizes a single icon (or a spinner during uploads).

AttachmentAction

Extends Button — all Button props apply. Defaults to variant="ghost" and size="icon-xs".

AttachmentTrigger

An invisible full-card button (swap the element via the Base UI render prop, for example render={<a href="…" />}). Give it an aria-label describing the open/preview action.

AttachmentGroup, AttachmentContent, AttachmentTitle, AttachmentDescription, AttachmentActions

Structural wrappers (div/span) accepting standard element props.

Was this page helpful?