Attachment
A compact card for a file attached to a message or a form, with its state, a trigger to open it and actions.
An attachment shows one file as a small card: an icon or a thumbnail, the name, the size, and
actions such as remove. It shows the state of an upload too, from idle to done or failed. Several
attachments sit in a row that scrolls sideways. For picking files, see the composed
Simple.
2 files attached
// apps/docs/examples/ui/attachment.tsx'use client';
import {useState} from 'react';import {FileSpreadsheetIcon, FileTextIcon, XIcon} from 'lucide-react';
import {formatByteSize} from '@workspace/active-format';import { Attachment, AttachmentAction, AttachmentActions, AttachmentContent, AttachmentDescription, AttachmentGroup, AttachmentMedia, AttachmentTitle, AttachmentTrigger,} from '@workspace/active-ui/components/primitives/attachment';
const FILES = [ {name: 'participants.csv', bytes: 48_213, icon: FileSpreadsheetIcon}, {name: 'scheme-rules-2026.pdf', bytes: 1_204_331, icon: FileTextIcon},];
/** * Files attached to a message or a form, as compact cards: an icon, the * name, the size (written by active-format), a trigger over the whole card * that opens the file, and an action that removes it. */export default function AttachmentExample() { const [files, setFiles] = useState(FILES); const [opened, setOpened] = useState<string | null>(null); return ( <div className="flex w-full max-w-md flex-col gap-3"> <AttachmentGroup> {files.map((file) => ( <Attachment key={file.name}> <AttachmentTrigger aria-label={`Open ${file.name}`} onClick={() => setOpened(file.name)} /> <AttachmentMedia> <file.icon aria-hidden /> </AttachmentMedia> <AttachmentContent> <AttachmentTitle>{file.name}</AttachmentTitle> <AttachmentDescription> {formatByteSize(file.bytes, 'en', {system: 'decimal'})} </AttachmentDescription> </AttachmentContent> <AttachmentActions> <AttachmentAction aria-label={`Remove ${file.name}`} onClick={() => setFiles((current) => current.filter((other) => other.name !== file.name), ) } > <XIcon aria-hidden /> </AttachmentAction> </AttachmentActions> </Attachment> ))} </AttachmentGroup> <p className="text-muted-foreground text-sm" aria-live="polite"> {opened ? `Opened ${opened}` : `${files.length} files attached`} </p> </div> );}Usage#
import { Attachment, AttachmentContent, AttachmentDescription, AttachmentMedia, AttachmentTitle, AttachmentTrigger,} from '@workspace/active-ui/components/primitives/attachment';
<Attachment> <AttachmentTrigger aria-label="Open participants.csv" onClick={open} /> <AttachmentMedia> <FileSpreadsheetIcon aria-hidden /> </AttachmentMedia> <AttachmentContent> <AttachmentTitle>participants.csv</AttachmentTitle> <AttachmentDescription>48 kB</AttachmentDescription> </AttachmentContent></Attachment>;Anatomy#
Attachment: the card.state:idle(a dashed border),uploading,processing,error(a red tone) ordone(default);size:default,smorxs;orientation:horizontalorvertical(a tile with a large thumbnail).Attachment: the icon, or a picture withMedia variant="image", dimmed until the file is done.AttachmentwithContent AttachmentandTitle Attachment(truncated).Description Attachment: a button over the whole card, to open the file; give it an accessible name.Trigger AttachmentwithActions Attachment: buttons above the trigger, such as remove.Action Attachment: a row of attachments that scrolls sideways with snapping.Group
What your application provides#
The file name, the size formatted with active-format (
format), and the state of an upload.Byte Size An accessible name for the trigger and for each icon action, such as "Remove participants.csv".
What opening and removing do.
Accessibility and keyboard#
The trigger and each action are separate tab stops.
A state shown only by colour (error) needs text too: say it in the description.
API#
Import from @workspace/
Props
- Type
"horizontal""vertical"null- Default
'horizontal'
horizontal is a row; vertical a tile with a large thumbnail.
- Type
"default""sm""xs"null- Default
'default'
The padding and text size: default, sm or xs.
- Type
"done""error""idle""processing""uploading"- Default
'done'
The state of the file: idle (not uploaded, a dashed border), uploading, processing, error (a red tone) or done.
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.
Other parts#
AttachmentMedia
Props
- Type
"icon""image"null- Default
'icon'
icon, or image for a thumbnail, dimmed until the file is done.
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.
AttachmentAction
Props marked Base UI are behaviour of the Base UI part this component is built on.
Props
- Type
boolean- Default
false
Whether the button should be focusable when disabled.
- Type
"default""icon""icon-lg" "icon-sm" "icon-xs" "lg""sm""xs"null- Default
'icon-xs'
The height and padding. xs, sm, default and lg for a button with text; icon-, icon-, icon and icon- for a square button with only an icon, which then needs an accessible name (aria-).
- Type
"default""destructive""ghost""link""outline""secondary"null
The visual weight of the button. default for the main action of a screen or form; outline and secondary for other actions; ghost for a quiet action in a toolbar or a list; destructive for an action that deletes or cannot be undone; link for an action that reads as a link. Every variant but link sinks 1px while pressed; a link-styled action keeps its text and icon in place (button-).
- Type
ComponentRender Fn<โฆ> ReactElement<โฆ>
Allows you to replace the component's HTML element with a different tag, or compose it with another component.
Accepts a React or a function that returns the element to render.
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.
Parts that take only their elementโs attributes
Attachment: the attributes of aGroup <div>, with className and ref.Attachment: the attributes of aContent <div>, with className and ref.Attachment: the attributes of aTitle <span>, with className and ref.Attachment: the attributes of aDescription <span>, with className and ref.Attachment: the attributes of aActions <div>, with className and ref.Attachment: the attributes of aTrigger <button>, with className and ref, and Base UI'srenderto use your own element.