Attachments

The file-attachment tray — item chips, a remove affordance, and inline or global drop zones.

quarterly-report.pdf242 KB
meeting-notes.txt1 KB

Usage guidelines

  • Pending-file tray — the strip above the composer input, showing attachment chips with a remove affordance.
  • Model included — accept matching, blob-URL lifecycle, and send preparation ship in the package; the styled tray animates items in and out.
  • Media-aware — each item exposes data-media-type (image / pdf / file) for per-type styling.
  • Drop + pick — a Dropzone overlay (inline, or global to project into the app shell) plus a Trigger file picker.
  • Get started — see Installation to add the package and copy the component.

Anatomy

<Attachments show={items.length > 0}>
  {items.map((item) => (
    <Attachments.Item key={item.id} item={item}>
      <Attachments.Remove onRemove={() => remove(item.id)} />
    </Attachments.Item>
  ))}
</Attachments>

With a drop zone and a picker trigger:

<>
  <Attachments.Dropzone visible={isDragging} variant="global" />
  <Attachments show={items.length > 0}>
    {items.map((item) => (
      <Attachments.Item key={item.id} item={item}>
        <Attachments.Remove onRemove={() => remove(item.id)} />
      </Attachments.Item>
    ))}
  </Attachments>
  <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 PrimitiveProps) and emits a bespoke part attribute (data-<part>) unless noted.

Attachments

The tray container; animates open/closed. Renders data-attachments.

PropTypeDefault
showboolean
true

Attachments.Item

One attachment chip. Renders data-attachments-item.

PropTypeDefault
itemAttachmentItem
(required)
AttributeValuesDescription
data-attachments-itemThe chip.
data-media-type"image" | "pdf" | "file"The item's media category, for per-type styling.

Attachments.Remove

Removal affordance, shown on hover. Renders <button data-attachments-remove> named "Remove attachment", or Remove {filename} when filename is set.

PropTypeDefault
onRemove() => void
(required)
filenamestring

Attachments.Dropzone

Drop overlay. Renders data-attachments-dropzone. variant="global" portals it into the app shell so files can be dropped anywhere.

PropTypeDefault
visibleboolean
false
variant"inline" | "global"
keepMountedboolean
false
portalSelectorstring
AttributeDescription
data-attachments-dropzoneThe overlay.
data-visiblePresent 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

The wrapper re-exports the attachment surface so you import everything from one place. From the package (generic mechanics): toAttachmentItem (the default blob ingestion), matchesAccept, and revokeAttachmentUrl. From the app (policy, taxonomy, send strategy): the ATTACHMENT_* accept/limit policy, isImageAttachment / isPdfAttachment, formatFileSize, and prepareAttachmentsForSend — the adapter that turns submitted items into AI SDK file parts. The package imposes none of these; they're yours to change.