Composer Attachments
The attachment strip for AI chat composers: progress rings that draw around each tile, retry, previews and drag and drop.
Overview
Files dropped or picked into a chat composer land in a strip above the input, one after another. Images get real thumbnails; documents, spreadsheets, PDFs, slides, code and archives get a typed file glyph with the name, extension and size.
While a file uploads, an accent ring draws clockwise around the tile’s own outline and a percentage counts up in the corner. At 100% the ring flashes green and fades, and the percentage blurs out as the dismiss button blurs in on the same spot. Failed uploads turn red with the reason and a retry button; files over your size limit fail without uploading.
It ships with useAttachmentUploads, a hook that takes real File objects and your own upload function (fetch, S3 presigned URLs, Supabase Storage, anything that reports progress), uploads them one at a time, cancels on remove and frees image thumbnails when they go. AttachmentDropZone turns any composer into a drop target.
When to use
- Chat and agent composers that accept files, images and documents.
- Any form where several files upload in the background while people keep typing.
When not to use
- A dedicated upload page with large files and folders: use Upload Queue.
- Single profile-photo uploads: use Avatar Upload.
Installation
pnpm dlx shadcn@latest add https://sundaeui.dev/r/composer-attachments.jsonnpx shadcn@latest add https://sundaeui.dev/r/composer-attachments.jsonyarn dlx shadcn@latest add https://sundaeui.dev/r/composer-attachments.jsonbunx --bun shadcn@latest add https://sundaeui.dev/r/composer-attachments.jsonWorks in any React project. Files are written to components/sundae/. Read the source in the stage’s Code view. Pro assets need a personal token. See authenticated installs.
- No dependencies beyond React 18 or later.
- Copy composer-attachments.tsx and composer-attachments.css into components/sundae/.
- The CSS file is imported by the component, so no extra configuration is needed.
- Import the component and use it as shown below.
Usage
import { AttachmentDropZone, ComposerAttachments, useAttachmentUploads } from '@/components/sundae/composer-attachments'
function Composer() {
const files = useAttachmentUploads({
maxSize: 25 * 1024 * 1024,
// Your uploader: report progress, honour the abort signal.
upload: (file, { onProgress, signal }) =>
new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('POST', '/api/upload')
xhr.upload.onprogress = (e) => onProgress(e.loaded / e.total)
xhr.onload = () => (xhr.status < 300 ? resolve() : reject(new Error('Upload failed')))
xhr.onerror = () => reject(new Error('Network error'))
signal.addEventListener('abort', () => xhr.abort())
xhr.send(file)
}),
})
return (
<AttachmentDropZone onFiles={files.addFiles}>
<ComposerAttachments items={files.items} onRemove={files.remove} onRetry={files.retry} />
<textarea placeholder="Ask anything" />
<input type="file" multiple onChange={(e) => e.target.files && files.addFiles(e.target.files)} />
</AttachmentDropZone>
)
}Props
| Prop | Type | Default |
|---|---|---|
items*Files to show: name, size, type, previewUrl, status (queued, uploading, done, error), progress 0–1, error. | AttachmentItem[] | — |
onRemove*Remove a file (and cancel its upload). | (id: string) => void | — |
onRetryRetry a failed upload. Without it, failed tiles only offer remove. | (id: string) => void | — |
previewOpen images in a lightbox when their tile is pressed. | boolean | true |
summaryShow the "3 files · 4.2 MB" line and upload progress under the strip. | boolean | true |
themeauto follows a .dark ancestor. | 'auto' | 'light' | 'dark' | 'auto' |
useAttachmentUploads({ upload, concurrency, maxSize, maxFiles })Manages real File objects: { items, addFiles, remove, retry, clear, uploading }. concurrency defaults to 1 (sequential). | hook | — |
<AttachmentDropZone onFiles label>Wrap the composer to accept dropped files, with an outline and label while dragging. | component | — |
Accessibility
- Every tile control is a real button with a specific label: “Remove report.pdf”, “Cancel upload of report.pdf”, “Retry report.pdf”, “Preview photo.jpg”.
- Upload completion and failure are announced through a polite status region per tile; progress is not announced on every tick.
- The image preview is a native modal dialog: focus moves in, Escape closes it, and focus returns to the tile.
- Reduced motion removes the entrance, ring, blur and settle animations; states still change instantly.
Customization
- Colours are custom properties on .sd-att: --att-accent (ring), --att-ok, --att-err, --att-tile, --att-chip.
- Tile sizes: images are 64 × 64, documents 184 × 64 (set by TILE in the source).
- File types map to glyphs through EXT and fileKind(); add your own extensions there.
FAQ
- Does it upload anything by itself?
- No. useAttachmentUploads calls the upload function you pass, with the File, an onProgress callback and an AbortSignal, so it works with any backend.
- Can several files upload at once?
- Yes: pass concurrency (default 1, which uploads them one after another).
- Is Composer Attachments free?
- Composer Attachments is part of Sundae Pro. The live preview is open to everyone; the source installs with a Pro account.
- How do I install Composer Attachments?
- Run npx shadcn@latest add https://sundaeui.dev/r/composer-attachments.json. The files are written to components/sundae/. It has no dependencies beyond React 18 or later.
- Does Composer Attachments support keyboard and screen readers?
- Every tile control is a real button with a specific label: “Remove report.pdf”, “Cancel upload of report.pdf”, “Retry report.pdf”, “Preview photo.jpg”. Upload completion and failure are announced through a polite status region per tile; progress is not announced on every tick.
- Does Composer Attachments respect reduced motion?
- Reduced motion removes the entrance, ring, blur and settle animations; states still change instantly.
More like this
All components →Agent Thinking
Components · Refined- Pro
Reasoning Trace
Components · Refined - Pro
Web Search
Components · Refined - Pro
Tool Call
Components · Refined - Pro
Cited Answer
Components · Refined - Pro
AI Composer
Components · Understated