Installation
Complete project setup first. Your configuration
must select react/aria and include your Design System ID.
pnpm exec frontfriend download attachment -f react --primitive ariaReview dependencies reported by the downloader and keep your source changes in version control.
Usage
import {
Attachment,
AttachmentMedia,
AttachmentContent,
AttachmentTitle,
AttachmentDescription,
} from "@/components/ui/attachment";
import { Icon } from "@/components/ui/icon";
<Attachment
orientation="horizontal"
state="done"
className="w-72"
>
<AttachmentMedia>
<Icon name="FileText" />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>report.pdf</AttachmentTitle>
<AttachmentDescription>PDF · 12 KB</AttachmentDescription>
</AttachmentContent>
</Attachment>; Examples
Inside Form
import {
Attachment,
AttachmentTrigger,
AttachmentMedia,
AttachmentContent,
AttachmentTitle,
AttachmentDescription,
} from "@/components/ui/attachment";
import { Icon } from "@/components/ui/icon";
import { Button } from "@/components/ui/button";
import * as React from "react";
export default function AttachmentExample() {
const [opened, setOpened] = React.useState(0);
const [submitted, setSubmitted] = React.useState(0);
return (
<form
className="flex w-[360px] max-w-[calc(100vw-2rem)] flex-col gap-3"
onSubmit={(event) => {
event.preventDefault();
setSubmitted((count) => count + 1);
}}
>
<Attachment>
<AttachmentTrigger
aria-label="Open report.pdf"
onClick={() => setOpened((count) => count + 1)}
/>
<AttachmentMedia>
<Icon name="FileText" size="4" />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>report.pdf</AttachmentTitle>
<AttachmentDescription>Open attachment</AttachmentDescription>
</AttachmentContent>
</Attachment>
<Button type="submit" variant="outline">
Submit form
</Button>
<output className="text-sm text-neutral-mid">
Opened: {opened}. Submitted: {submitted}.
</output>
</form>
);
} Default
import {
Attachment,
AttachmentMedia,
AttachmentContent,
AttachmentTitle,
AttachmentDescription,
AttachmentActions,
AttachmentAction,
} from "@/components/ui/attachment";
import { Icon } from "@/components/ui/icon";
<div className="flex w-full max-w-sm flex-col gap-3">
<Attachment className="w-full">
<AttachmentMedia>
<Icon name="FileCode" size="4" />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>message-renderer.tsx</AttachmentTitle>
<AttachmentDescription>TypeScript · 12 KB</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<AttachmentAction aria-label="Remove message-renderer.tsx">
<Icon name="X" size="4" />
</AttachmentAction>
</AttachmentActions>
</Attachment>
</div>; Image Group
import {
AttachmentGroup,
Attachment,
AttachmentMedia,
AttachmentContent,
AttachmentTitle,
AttachmentDescription,
} from "@/components/ui/attachment";
const images = [
{
name: "workspace.png",
meta: "PNG · 820 KB",
src: "https://github.com/shadcn.png",
alt: "Workspace",
},
{
name: "desk-reference.jpg",
meta: "JPG · 1.1 MB",
src: "https://github.com/vercel.png",
alt: "Desk",
},
];
<div className="flex w-full max-w-sm flex-col gap-3">
<AttachmentGroup>
{images.map((image) => (
<Attachment key={image.name} 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>
</div>; Uploading
import {
Attachment,
AttachmentMedia,
AttachmentContent,
AttachmentTitle,
AttachmentDescription,
AttachmentActions,
AttachmentAction,
} from "@/components/ui/attachment";
import { Spinner } from "@/components/ui/spinner";
import { Icon } from "@/components/ui/icon";
<div className="flex w-full max-w-sm flex-col gap-3">
<Attachment state="uploading" className="w-full">
<AttachmentMedia>
<Spinner />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>sales-dashboard.pdf</AttachmentTitle>
<AttachmentDescription>Uploading · 64%</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<AttachmentAction aria-label="Cancel upload">
<Icon name="X" size="4" />
</AttachmentAction>
</AttachmentActions>
</Attachment>
</div>; States
import {
Attachment,
AttachmentMedia,
AttachmentContent,
AttachmentTitle,
AttachmentDescription,
} from "@/components/ui/attachment";
import { Icon } from "@/components/ui/icon";
<div className="flex w-full max-w-sm flex-col gap-3">
{(["idle", "uploading", "processing", "error", "done"] as const).map(
(state) => (
<Attachment key={state} state={state} className="w-full">
<AttachmentMedia>
<Icon name="FileText" size="4" />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>report.pdf</AttachmentTitle>
<AttachmentDescription>{state}</AttachmentDescription>
</AttachmentContent>
</Attachment>
),
)}
</div>; API reference
Frontfriend-owned props. Native HTML and inherited primitive props remain documented by React Aria.
Attachment
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | unknown | "horizontal" | |
size | unknown | "default" | |
state | "idle" | "uploading" | "processing" | "error" | "done" | "done" |
AttachmentMedia
| Prop | Type | Default | Description |
|---|---|---|---|
variant | unknown | "icon" |
AttachmentTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false |
Configuration determines the actual classes and available styling hooks. See Theming & component configuration before introducing a repeated local override.