Attachment
A file or image with its name, details, upload state and actions.
<Attachment> <AttachmentMedia> <PdfIcon /> </AttachmentMedia> <AttachmentContent> <AttachmentTitle>q3-report.pdf</AttachmentTitle> <AttachmentDescription>PDF · 2.4 MB</AttachmentDescription> </AttachmentContent> <AttachmentActions> <AttachmentAction aria-label="Remove q3-report.pdf"> <CancelIcon /> </AttachmentAction> </AttachmentActions></Attachment>Installation
$ npx shadcn@latest add @constella/attachmentUsage
import { Attachment, AttachmentAction, AttachmentActions, AttachmentContent, AttachmentDescription, AttachmentGroup, AttachmentMedia, AttachmentTitle, AttachmentTrigger,} from "@/components/ai/attachment";<Attachment> <AttachmentMedia> <FileIcon /> </AttachmentMedia> <AttachmentContent> <AttachmentTitle>notes.md</AttachmentTitle> </AttachmentContent></Attachment>Composition
AttachmentGroup└── Attachment ├── AttachmentTrigger ├── AttachmentMedia ├── AttachmentContent │ ├── AttachmentTitle │ └── AttachmentDescription └── AttachmentActions └── AttachmentActionSize
Use the size prop to fit a composer or a dense list.
<Attachment size="sm">…</Attachment>| Size | Media | Text | Lines |
|---|---|---|---|
default | 40px | 13px | Two: title, then description |
sm | 32px | 13px | Two: title, then description |
xs | 28px | 13px | One: title and description side by side |
Keep xs to one short line of content.
Media
Use variant="image" on AttachmentMedia for a picture, with orientation="vertical" on Attachment to stack it above the text. The image fills the media box and stays dimmed until the file is in.
<Attachment orientation="vertical"> <AttachmentMedia variant="image"> <img alt="" src={url} /> </AttachmentMedia> <AttachmentContent> <AttachmentTitle>workspace.png</AttachmentTitle> <AttachmentDescription>PNG · 180 KB</AttachmentDescription> </AttachmentContent></Attachment>Trigger
Add AttachmentTrigger to open the file from anywhere on the card. Use its render prop for a link or a Dialog's trigger; the actions stay their own buttons above it.
<Dialog> <Attachment> <AttachmentTrigger aria-label="Preview q3-report.pdf" render={<DialogTrigger />} /> … </Attachment> <DialogContent>…</DialogContent></Dialog>Group
Wrap several in AttachmentGroup for a row that scrolls and snaps, with a Scroll Fade on its edges.
<AttachmentGroup> {files.map((file) => ( <Attachment key={file.name}>…</Attachment> ))}</AttachmentGroup>State
Use the state prop to show where an upload is. Say why a file failed in AttachmentDescription, so the error isn't color alone.
<Attachment state="uploading"> <AttachmentMedia> <Spinner /> </AttachmentMedia> <AttachmentContent> <AttachmentTitle>q3-report.pdf</AttachmentTitle> <AttachmentDescription>Uploading · 45%</AttachmentDescription> </AttachmentContent></Attachment>| State | Looks like |
|---|---|
idle | A dashed edge, ready to upload |
uploading | A shimmering title, a dimmed image |
processing | The same, while the server reads it |
error | A red tint on the media and its note |
done | The default |
API Reference
Every part takes sx, applied last. AttachmentTitle and AttachmentDescription render a span, AttachmentTrigger a button, the rest a div.