ComponentsData display

Attachment

Shows a file a person attached, uploaded, or referenced. UseFileAttachment for a ready-made row, card, mention, or chip, and the Attachment parts when you need your own layout.

Attachment parts

Compose media, content, and actions. State changes the border, title shimmer, and error color; size and orientation set the footprint.

States
Supplier contacts.csvWaiting to upload
Price list 2026.xlsxUploading 1.2 MB of 3.4 MB
Returns policy.pdfReading 14 pages…
Store floor plan.pdfThis file is 14 MB. The upload limit is 10 MB.
Onboarding checklist.docxDocument · 31 KB
Sizes and orientations
default
Greenhouse layout.pngImage · 1.4 MB
Greenhouse layout.pngImage · 1.4 MB
sm
Seed tray.png220 KB
Seed tray.png220 KB
xs
Logo.pngPNG
Logo.pngPNG
Whole-card trigger
Q3 launch deck.pptxSlides · 2.8 MB

Nothing opened yet

File attachment variants

A ready-made row, card, inline mention, or chip. Uploading and processing files show a spinner, failed files offer Retry, and each can be removed.

list
Storefront.png
grid
inline
Failed
chip

No action yet

Preview and open

preview peeks at images, text, and code on hover or focus. viewable opens the file full screen on click, with syntax colors for every language. Inside a group, the arrows and arrow keys step through its files.

chip
list
Storefront.svg

Composer uploads

Staged files scroll sideways in a carousel. Click a chip to preview it, use the arrows or Left and Right keys to scroll, and remove or retry each file.

Price list 2026.xlsx: Ready to send · Storage only, not read by this chat

Returns policy.pdf: Uploading for private storage…

Onboarding checklist.docx: Ready to send · Storage only, not read by this chat

garden-notes.txt: Upload failed. Check your connection and retry.

Spring planting plan.md: Ready to send · Storage only, not read by this chat

inventory.json: Ready to send · Storage only, not read by this chat

Q3 launch deck.pptx: Ready to send · Storage only, not read by this chat

Sent references

Chips in a sent message point at Files or Google Drive. A loading reference shows a disabled chip; generated files offer Download.

Compare the 2026 prices with last year’s, and flag anything that went up more than 10%.

No file opened

Group layouts

carousel scrolls one row, grid makes two columns (three from 640px), inline wraps, and list stacks.

carousel
grid
inline
list
Greenhouse layout.png

File type icons

A decorative paper glyph with a colored extension badge. sm (20 × 24) sits in rows and chips; lg (30 × 36) fills grid previews.

Returns policy.pdf
Price list 2026.xlsx
Onboarding checklist.docx
Q3 launch deck.pptx
Storefront.png
inventory.json
garden-notes.txt
Spring plan.md
Supplier sheet.gsheet
export.zip
Unknown name
Files table
  • Contracts6 itemsYou
  • Price list 2026.xlsxSpreadsheet · 48 KBYou
  • Returns policy.pdfPDF · 212 KBRobin
  • Onboarding checklist.docxDocument · 31 KBYou
Uploads
  • Store floor plan.pdfUploading 2.1 MB of 6.4 MB · Contracts
  • Staff rota.xlsxUploaded · Projects / Spring launch
  • Supplier photos.pngThis file is 14 MB. The upload limit is 10 MB. · Files
  • Meeting notes.docxFailed · Files
Message editor mention

SummarizeReturns policy.pdfand compare it withFilebefore Friday.

Table file field
  • Signed lease.pdf
  • Floor plan.png
Task files to read
  • Returns policy.pdf
  • Supplier price sheetGoogle Drive
Dialog file heading and Drive picker

Supplier notes from the March visit.docx

  • Supplier price sheetGoogle Sheets
  • Delivery terms.pdfPDF · Proposed new Drive file · 84 KB

Local file picker

Drop files or choose them. Files stay in browser memory; nothing is uploaded. Images get a live thumbnail.

Default

Drop files here, or choose them from your device.

Project knowledge, text and code only

Drop files here, or choose them from your device.

Disabled while saving

Drop files here, or choose them from your device.

File attachments

Staged uploads in a composerTSX
import { AttachmentGroup, FileAttachment } from "@tulipfarm/sprout/attachment";

<AttachmentGroup variant="carousel" aria-label="Attached files">
  {uploads.map((item) => (
    <FileAttachment
      key={item.id}
      variant="chip"
      file={{
        id: item.id,
        name: item.file.name,
        mediaType: item.file.type,
        size: "48 KB",
        state: item.kind, // "ready" | "uploading" | "processing" | "error"
        source: { kind: "local", file: item.file },
      }}
      preview
      viewable
      onRemove={() => remove(item.id)}
      onRetry={() => retry(item)}
    />
  ))}
</AttachmentGroup>

variant picks the shape:

  • "list" (default): a row with icon, name, and status line.
  • "grid": a card with a large preview above the name.
  • "inline": a compact pill for text.
  • "chip": a 36px chip for composers and messages.

The status line shows file.size when ready, which can also be a short source such as “Google Drive” or “Saved in Files”. Busy files show a spinner, and "error" shows “Upload failed” with a Retry button when you pass onRetry. onRemoveadds a remove button. Images with a url show a thumbnail.

Opening: viewable opens the file in the full-screenFileViewer on click, with syntax colors for code and text. PassonOpen instead to open it your own way, such as a canvas or a download; it is disabled while the file is busy. With neither, the name is plain text.actionLabel="Download"names the button “Download …” instead of “Inspect …”.

preview peeks at the file in a card after hovering or focusing the name for 400ms: the image, or the first 24 lines of text, code, JSON, or Markdown. Other formats and failed uploads get no card. Both props need file.source or file.url. Wrap the app in FileRenderersProvider to view sheets, Office files, and formatted Markdown.

References in a sent messageTSX
<FileAttachment
  variant="chip"
  file={{ id, name: "Supplier price sheet", mediaType, size: "Google Drive", state: "ready" }}
  onOpen={openInCanvas}
/>

<FileAttachment
  variant="chip"
  file={{ id, name: "Loading file…", mediaType: "", size: "", state: "processing" }}
  onOpen={() => undefined}
/>

<FileAttachment
  variant="chip"
  actionLabel="Download"
  file={{ id, name: "Price comparison.xlsx", mediaType, size: "52 KB", state: saving ? "processing" : "ready" }}
  onOpen={save}
/>

Groups

AttachmentGroup lays out attachments. "carousel"(default) scrolls one row sideways with fading edge arrows;"grid" makes two columns, three from 640px;"inline" wraps; "list" stacks. Give a carousel anaria-label. Viewable attachments in a group share one viewer that steps through them in page order with Previous, Next, and the arrow keys. Closing it returns focus to the last file shown.

Parts

Build your own attachmentTSX
<Attachment state="uploading" size="sm" orientation="horizontal">
  <AttachmentTrigger aria-label="Open Q3 launch deck.pptx" onClick={open} />
  <AttachmentMedia>
    <FileTypeIcon name="Q3 launch deck.pptx" mediaType={type} />
  </AttachmentMedia>
  <AttachmentContent>
    <AttachmentTitle>Q3 launch deck.pptx</AttachmentTitle>
    <AttachmentDescription>Uploading 1.2 MB of 2.8 MB</AttachmentDescription>
  </AttachmentContent>
  <AttachmentActions>
    <AttachmentAction aria-label="Remove Q3 launch deck.pptx" onClick={remove}>
      <X />
    </AttachmentAction>
  </AttachmentActions>
</Attachment>

  • state: "idle" dashes the border,"uploading" and "processing" shimmer the title,"error" turns it red, "done" (default) is plain.
  • size: "default" (40px media),"sm" (32px), "xs" (28px).
  • orientation: "horizontal" (default) or"vertical", which stacks the media above the text and floats actions over its corner.
  • AttachmentMedia variant="image" crops animg; the default "icon" centers an icon or spinner.
  • AttachmentTrigger stretches over the card so a click anywhere opens it. It accepts render for a link.

File type icons

Rows, mentions, and dialog headingsTSX
<FileTypeIcon name="Returns policy.pdf" mediaType="application/pdf" />
<FileTypeIcon name="Price list 2026.xlsx" mediaType={type} size="lg" />

A decorative paper glyph with the extension in a colored badge: red for PDF, green for sheets, blue for documents and Google files, orange for slides, light blue for images, and gray for everything else. "sm" (default) is 20 × 24px; "lg" is 30 × 36px. A name without an extension shows no badge.

Local files

Drop zone with in-memory filesTSX
<AttachmentDropzone accept=".txt,.md,.pdf,.docx" disabled={busy} onFiles={add} />
{files.map((file, index) => (
  <LocalFileAttachment key={index} file={file} preview viewable onRemove={() => remove(index)} />
))}

AttachmentDropzone takes drops or a “Choose files” button and hands the files to onFiles. It uploads nothing.accept defaults to documents, sheets, slides, PDF, text, JSON, Google shortcuts, images, and common source files.LocalFileAttachment shows a File as a chip with a live image thumbnail, and passes preview andviewable through.

Accessibility

Each open button is named “Inspect name” or “Download name”; remove and retry buttons are named “Remove name” and “Retry name”. Busy chips have a spinner named “Uploading…” or “Processing…”. File type icons are hidden from assistive tech because the name is always beside them.

The carousel is a labelled region. Each item is a group, focus scrolls it into view, and Left and Right scroll it when focus is inside. The edge arrows are for pointers, so Tab never lands on a button that vanishes at the end of the row. The drop zone's button opens the system file picker. Transitions take 120 to 180ms; with reduced motion the title shimmer and smooth scrolling stop.

Theme tokens

Cards use --sprout-surface, --sprout-line,--sprout-hover, and --sprout-danger. File type icons use --sprout-file-paper,--sprout-file-edge, --sprout-file-label, and one badge color per kind: --sprout-file-generic,-pdf, -sheet, -document,-slides, and -image. Each has a light and a dark value.