Attachments
The attachment tray — structural slots for items, a remove affordance, a drop zone, and a file picker.
Usage guidelines
- Pending-file tray — the strip above the composer input, showing attachment chips with a remove affordance.
- Model included — accept matching and blob-URL lifecycle ship in the package; the tray's layout and motion are yours.
- No media taxonomy —
Attachments.Itemis a structural slot; read the item'smediaTypeand decide what an image, a PDF, or a file looks like. - Drop + pick — a
Dropzoneoverlay (in place, or portalled elsewhere viaportalSelector) plus aTriggerfile picker. - Get started — see Quick start to add the package.
Anatomy
{items.length > 0 && (
<Attachments.Root>
{items.map((item) => (
<Attachments.Item key={item.id}>
<span>{item.filename}</span>
<Attachments.Remove onRemove={() => remove(item.id)} filename={item.filename} />
</Attachments.Item>
))}
</Attachments.Root>
)}With a drop zone and a picker trigger:
<>
<Attachments.Dropzone visible={isDragging} portalSelector="#app-shell" />
<Attachments.Root>
{items.map((item) => (
<Attachments.Item key={item.id}>
<span>{item.filename}</span>
<Attachments.Remove onRemove={() => remove(item.id)} filename={item.filename} />
</Attachments.Item>
))}
</Attachments.Root>
<Attachments.Trigger onClick={openFileDialog} />
</>Accessibility
Attachments.Remove and Attachments.Trigger are real buttons with default
overridable names ("Remove attachment" / "Add attachment"). Pass filename to
Remove so each chip announces distinctly ("Remove report.pdf") — without it,
N remove buttons all read the same. Attachments.Error is a role="alert"
live region: content appearing inside it announces immediately (the copy is
still yours).
API reference
Every part accepts className, style, and render (see
Styling) and emits a bespoke part attribute (data-<part>) unless noted.
Attachments
The tray container. Renders data-attachments and takes no props of its own —
mount it conditionally when there are items to show.
Attachments.Item
One attachment chip — a structural slot with no props of its own. Render the
filename, size, thumbnail or icon inside it however you like. Renders
data-attachments-item.
| Attribute | Description |
|---|---|
data-attachments-item | The chip. |
Attachments.Remove
Removal affordance, shown on hover. Renders <button data-attachments-remove>
named "Remove attachment", or Remove {filename} when filename is set.
| Prop | Type | Default |
|---|---|---|
onRemove | () => void | (required) |
filename | string | — |
Attachments.Dropzone
Drop overlay. Renders data-attachments-dropzone. Pass portalSelector to
portal it elsewhere — into the app shell, for instance, so files can be dropped
anywhere.
| Prop | Type | Default |
|---|---|---|
visible | boolean | false |
keepMounted | boolean | false |
portalSelector | string | — |
| Attribute | Description |
|---|---|
data-attachments-dropzone | The overlay. |
data-visible | Present while visible is true. |
Attachments.Error
Validation message slot. Renders <span role="alert" data-attachments-error>,
so content appearing inside it announces immediately. Validation emits a
structured AttachmentErrorCode ("accept", "max_file_size", "max_files")
on the composer's attachments state — you map codes to your own (localized)
copy; the package ships no messages.
Attachments.Trigger
The file-picker button. Renders <button data-attachments-trigger> named
"Add attachment" by default.
Utilities
@intentface/chat/attachments exports the generic mechanics:
toAttachmentItem (the default blob ingestion), matchesAccept, and
revokeAttachmentUrl.
Everything above that is yours: the accept and size policy, the media taxonomy that decides what an image or a PDF looks like, and the adapter that turns submitted items into whatever your transport expects — AI SDK file parts, signed uploads, or anything else. The package imposes none of it.