Attachments tray
Files on their way into a chat: a tray above the input with progress and retry, a drop zone that checks what it accepts, a grid of images to reorder, files weighed against the context limit, and how they look once sent.
Loading preview…
Install
- 1
Run this command in your Tailwind CSS project. It adds
components/ai/attachments-tray-default.tsxand installs its dependencies.$ npx shadcn@latest add https://gradientdeck.com/ai-components/r/attachments-tray-default.json
Usage & API
Example
import { Scene } from "@/components/ai/attachments-tray-default"
<Scene
scene={scene}
uid="…"
/>Props Scene
| Prop | Type & description | Required |
|---|---|---|
scene | TrayItem["scene"] | Required |
uid | string | Required |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Above the input exports AttachmentsTray. An item has id, name, kind of image, pdf, sheet or doc, size, optional pages, progress, optional error, and an optional scene of kitchen, floorplan or hallway for drawn image previews. Callbacks are onRemove, onRetry and onOpen. Images are square tiles. The button name is Preview plus the name when the upload is done, the name plus uploading and the percent while progress is under 1, or the name plus upload failed when error is set. A failed image shows Retry on the tile. Other files show a type label, PDF, DOCX or XLSX, then pages or sheets, then size, or Uploading and the percent, or the error string beside a Retry text button. Remove is a small button labelled Remove plus the name. The demo opens a preview dialog labelled with the file name and a Close preview button, and Escape closes it. The field placeholder is Ask about these files.
Example
import { DropTray } from "@/components/ai/attachments-tray-drop"
<DropTray
files={[]}
rejections={[]}
onRemove={() => {}}
onDismiss={() => {}}
/>Props DropTray
| Prop | Type & description | Required |
|---|---|---|
files | DroppedFile[] | Required |
rejections | Rejection[] | Required |
onRemove | (id: string) => void | Required |
onDismiss | (id: string) => void | Required |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Drop zone exports DropTray, checkFile, formatBytes, ACCEPTED and MAX_BYTES. Props are files, rejections, onRemove and onDismiss. A file chip shows an icon by extension, the name, and either the percent or the formatted size once progress reaches 1. A two-pixel bar runs along the bottom until then. Remove uses the file name. Each rejection is an alert with the message and a Dismiss button. The demo adds the drop surface: a dashed overlay reading Drop to attach, a hidden file input behind Attach files, and a Downloads list used as a drag source. Dropped names run through checkFile. setup.exe and a large mov in that list are there to be rejected. The tray itself does not listen for drag events. The demo's form does, and it also keeps a drag-depth count so leaving a child does not flicker the overlay off.
Example
import { ImageGrid } from "@/components/ai/attachments-tray-images"
<ImageGrid
images={[]}
max={1}
onReorder={() => {}}
onRemove={() => {}}
onToggleReference={() => {}}
/>Props ImageGrid
| Prop | Type & description | Required |
|---|---|---|
images | GridImage[] | Required |
max | number | Required |
onReorder | (images: GridImage[]) => void | Required |
onRemove | (id: string) => void | Required |
onToggleReference | (id: string) => void | Required |
onAdd | () => void | Optional |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Image grid exports ImageGrid. Props are images, max, onReorder, onRemove, onToggleReference and onAdd. An image has id, name, colors, a shape of vase, mug, bowl or bottle, and optional reference. The list is labelled Attached images. Each cell is a button with role description sortable image. The accessible name includes the name, the index, the length, the word reference when it is marked, and Arrow keys move it. Dragging reorders by hit-testing the pointer against the other cells. Arrow keys swap with the neighbor and stop at the ends. A badge shows the index. Remove is labelled with the name. The reference button reads Mark or Unmark, the name, and as reference, and it shows the word Reference while pressed. Add image renders only when onAdd is passed and the length is under max. The demo shows N of 10, and either No reference picked or a reference count, next to a field labelled Describe the edit.
Example
import { ContextBudget } from "@/components/ai/attachments-tray-context"
<ContextBudget
files={[]}
limit={1}
used={1}
onToggle={() => {}}
onRange={() => {}}
/>Props ContextBudget
| Prop | Type & description | Required |
|---|---|---|
files | BudgetFile[] | Required |
limit | number | Required |
used | numberTokens already used by the conversation. | Required |
onToggle | (id: string) => void | Required |
onRange | (id: string, range: [number, number]) => void | Required |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Context budget exports ContextBudget, costOf and formatTokens. Props are files, limit, used, onToggle and onRange. A file has id, name, kind of pdf, doc or sheet, tokens, included, optional pages and range, a color and a detail string. The meter is labelled Context used, with valuemax set to limit and valuenow set to the total. The caption reads Context, then Sonnet 5.5, which is hardcoded copy, then the total of the limit. A legend row says This chat and the formatted used count. Each row has an include switch labelled Include plus the name. A PDF shows Pages, the from and to, of the page count, and opens a dialog labelled Page range with From and To numbers. Presets in that dialog are Letter and highlights for pages 1 to 12, Financial statements for 48 to 71, and Whole report. Turning a file off strikes through its token count. When the total exceeds the limit, an alert says how many tokens over, and tells you to narrow the page range or leave a file out. The demo limit is 200,000 and the chat used is 9,800. Send is disabled while over, or while the message is empty.
Example
import { SentAttachments } from "@/components/ai/attachments-tray-sent"
<SentAttachments
attachments={[]}
onOpen={() => {}}
/>Props SentAttachments
| Prop | Type & description | Required |
|---|---|---|
attachments | SentAttachment[] | Required |
onOpen | (id: string) => void | Required |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
In a sent message exports SentAttachments and AttachmentViewer. An attachment is an image with a scene of tram or rooftops, a pdf with pages of heading and lines, or a sheet with rows and an optional flag cell. SentAttachments takes attachments and onOpen. Images lay out in two columns. Other files sit in a one-column grid that becomes two columns from sm, with a thumb, the name, and a meta line: PDF, the page count, pages, and the size, or XLSX, the row count minus the header, rows, and the size. The viewer is a modal dialog labelled with the file name. Previous attachment, Next attachment and Close sit in the header, plus an index such as 1 of 4. Arrow keys move between attachments and Escape closes. A PDF adds Previous page and Next page, with Page n of m, and those buttons disable at the ends. A flagged sheet cell gets an amber outline. The demo opens the viewer from the chips and also from links inside the assistant paragraph, such as the itinerary and the budget file.
Choosing a layout
Use Above the input for a normal chat attach flow: photos, PDFs and sheets, with upload percent and a retry. The demo blocks send while any item is still uploading and has no error, and the button name becomes Send, waiting for uploads. A live line says Uploading, or 1 file didn't upload. Use Drop zone when people drag from the desktop and you must say no to a video or an installer. The overlay copy is Drop to attach, and PDF, DOCX, CSV, XLSX, PNG or JPG, up to 30 MB each. txt and md are accepted by checkFile even though that sentence does not name them. Use Image grid for a visual edit, where order and a reference shot matter. The demo caps the set at 10.
Use Context budget before a send that might blow the window, especially when a report should contribute a page range rather than the whole file. Context meter is the quieter summary of a conversation that is already in the window. It has no include switch and no page dialog. Use In a sent message once the files belong to a bubble. The viewer is a dialog with previous and next attachment, and PDFs also get previous and next page. Do not keep the upload tray mounted after send if the message variant is now the source of truth.
The drop overlay says JPG, but a markdown file still attaches. Which rule wins?
checkFile wins if you use it. ACCEPTED includes txt and md, and also jpeg, which the overlay sentence does not list. The overlay is copy in the demo, not a second validator. A rejection message is whatever checkFile returned, shown in the alert until onDismiss. If you want the sentence and the check to match, change the copy. The function will still refuse an extension that is not in the list, and anything over 30 MB.
Does marking a reference image change the prompt?
No. onToggleReference only tells you the id. The grid stores reference on the image you pass back in. The demo counts how many are marked and prints that next to the field, including the singular reference. Describe the edit is a separate textarea. When you submit, read both the text and the images array, and treat reference as your own signal for which shot the edit should follow. Remove and reorder are also callbacks. The grid does not upload and does not call a model.