Attachment Chip
A file attachment chip — a status-aware label with an optional image thumbnail and a hover preview (image, PDF, or file summary). Shared by the chat composer and sent-message rows.
Q3-drift-report.pdf
whitfield-fee-schedule.xlsx
'%2F%3E%3Ctext%20x%3D'50%25'%20y%3D'50%25'%20fill%3D'white'%20font-family%3D'sans-serif'%20font-size%3D'28'%20text-anchor%3D'middle'%20dominant-baseline%3D'middle'%3Eportfolio-chart.png%3C%2Ftext%3E%3C%2Fsvg%3E)
portfolio-chart.png
<AttachmentChip filename="portfolio-chart.png" mediaType="image/png" hasPreview previewStatus="ready" previewUrl={objectUrl}/>Usage
import { AttachmentChip } from "@reva/ui";AttachmentChip renders a single file attachment. It has two surfaces, chosen by whether you pass a status:
- Message chip (no
status) — a plain bordered chip for a file already attached to a sent message. - Composer chip (
statusset) — a status-aware chip for a file being uploaded:uploadingshows a spinner,errorrenders a danger-toned chip, andonRemoveadds an X button.
Image files render a ~48px inline thumbnail once previewUrl resolves, and any chip with hasPreview reveals a larger preview on hover.
PromptInputAttachment is a thin wrapper over this primitive, so composer chips and message chips share one surface.
Presentation only — you own the fetch
AttachmentChip does no fetching. It is pure presentation: you load the bytes (subject to your own auth / API) and feed the chip a resolved object URL plus the fetch status.
previewUrl— the object URL for the loaded bytes. Drives the image thumbnail and the hover-card body.previewStatus—idle/loading/ready/error, drives the hover-card spinner and error fallback.hasPreview— when true, the chip wraps itself in a hover card and emitsonPreviewOpenChange(open)so you can lazy-fetch the bytes only when the preview opens. Load images eagerly (passpreviewUrlup front) so the thumbnail paints without a hover.
function ChatAttachment({ documentId, filename, mediaType }) {
const [open, setOpen] = useState(false);
const preview = useDocumentPreview(documentId, open || mediaType?.startsWith("image/"));
return (
<AttachmentChip
filename={filename}
mediaType={mediaType}
hasPreview={!!documentId}
previewUrl={preview.objectUrl}
previewStatus={preview.status}
onPreviewOpenChange={setOpen}
/>
);
}Examples
Message chips
A plain bordered chip per attached file. Image files paint an inline thumbnail when previewUrl is supplied.
Q3-drift-report.pdf
whitfield-fee-schedule.xlsx
'%2F%3E%3Ctext%20x%3D'50%25'%20y%3D'50%25'%20fill%3D'white'%20font-family%3D'sans-serif'%20font-size%3D'28'%20text-anchor%3D'middle'%20dominant-baseline%3D'middle'%3Eportfolio-chart.png%3C%2Ftext%3E%3C%2Fsvg%3E)
portfolio-chart.png
<AttachmentChip filename="Q3-drift-report.pdf" mediaType="application/pdf" /><AttachmentChip filename="whitfield-fee-schedule.xlsx" mediaType="application/vnd.ms-excel" /><AttachmentChipfilename="portfolio-chart.png"mediaType="image/png"hasPreviewpreviewStatus="ready"previewUrl={objectUrl}/>Upload statuses
Composer chips across the upload lifecycle — uploading, ready, and error. Pass onRemove to add the X button.
<AttachmentChip filename="cashflow-2025.csv" status="uploading" /><AttachmentChip filename="isa-transfer-form.pdf" status="ready" onRemove={remove} /><AttachmentChip filename="kyc-pack.zip" status="error" onRemove={remove} />Hover preview
With hasPreview, hover reveals a larger preview: images render inline, PDFs in an iframe, and everything else as a tidy file summary.
'%2F%3E%3Ctext%20x%3D'50%25'%20y%3D'50%25'%20fill%3D'white'%20font-family%3D'sans-serif'%20font-size%3D'28'%20text-anchor%3D'middle'%20dominant-baseline%3D'middle'%3Eportfolio-chart.png%3C%2Ftext%3E%3C%2Fsvg%3E)
portfolio-chart.png
cashflow-2025.csv
<AttachmentChip filename="portfolio-chart.png" mediaType="image/png" hasPreview previewStatus="ready" previewUrl={objectUrl}/>Preview fetch states
The hover card reflects previewStatus: a spinner while loading, the bytes once ready, and a fallback when the fetch fails.
portfolio-chart.png
Hover the chip — the preview card reflects the selected fetch state.
Accessibility
- The remove button is named
"Remove {filename}". uploadingchips give the spinner an accessible"Uploading {filename}"label;errorchips carry a visually hidden"Upload failed".- The chip's hover trigger is a wrapper
div(not a button) so the composer chip can nest a real remove button without invalid nested-button HTML.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
filename | string | — | The file label; also names the remove button. Required. |
mediaType | string | — | MIME type — selects the type glyph and the hover-preview renderer (image / PDF / summary). |
status | "uploading" | "ready" | "error" | — | Set it for a composer chip (status glyph + optional remove). Omit it for the plain message chip. |
onRemove | () => void | — | Renders the remove (X) button when provided. |
previewUrl | string | — | Resolved object URL for the loaded bytes — drives the image thumbnail and the hover-card body. The component never fetches; you supply this. |
previewStatus | "idle" | "loading" | "ready" | "error" | — | Preview-fetch lifecycle — drives the hover-card spinner / error fallback. |
hasPreview | boolean | false | Wrap the chip in a hover card and emit onPreviewOpenChange. Omit for a bare chip with no preview. |
onPreviewOpenChange | (open: boolean) => void | — | Fired with the hover-open state so you can gate (lazy-load) the byte fetch. |
Voice Waveform
A live recording waveform whose bars track the microphone level, so a voice control visibly reacts as the person speaks.
Suggestion
Prompt-starter chips on a horizontal scroll row — staggered entrance, a neutral pill that tints on hover, and the suggestion string handed straight to onClick.