Marble UI
← Components

Chat Attachments

Preview: Chips

Attachment chips for prompts and messages: image thumbnails, file type badge, upload progress, remove.

Import

import { ChatAttachments } from '@marbleui/ui'

Props

<ChatAttachments>

PropTypeDefaultDescription
items *requiredChatAttachment[]—
classNamestring—
onRemove(id: string) => void—
variant'chips' | 'tiles''chips'

Usage notes

File chips for chat: image thumbnails, a file-type badge, size, upload progress and errors. PromptInput already shows it for files picked in the composer. Use it directly for the files on a sent ChatMessage, or in a composer you build yourself.

<ChatAttachments
  variant="tiles"
  items={[
    { id: 'a1', name: 'q3-report.pdf', type: 'application/pdf', size: 2_400_000 },
    { id: 'a2', name: 'mockup.png', type: 'image/png', url: previewUrl, progress: 62 },
  ]}
  onRemove={(id) => cancelUpload(id)}
/>
  • Leave out onRemove for a read-only list. Remove buttons are named per file (“Remove q3-report.pdf”).
  • Only image/* items with a url get a thumbnail. tiles shows those as small squares (other files stay chips); their remove button appears only on hover or keyboard focus, so touch users can’t see it.
  • On chips, progress (0–100) shows “Uploading… 62%” and error replaces the size; image tiles show a progress ring and no error. Neither change is announced.
  • PromptInput’s own attachments have no progress or error state, and its onSubmit returns these objects, not Files.
Example source · show
import { useState } from 'react'
import { ChatAttachments, type ChatAttachment } from './chat-attachments'

const img = (id: string) => `https://images.unsplash.com/photo-${id}?w=200&h=200&fit=crop&q=60`
const initial: ChatAttachment[] = [
  { id: '1', name: 'q3-report.pdf', type: 'application/pdf', size: 2_400_000 },
  { id: '2', name: 'mockup.png', type: 'image/png', size: 820_000, url: img('1500530855697-b586d89ba3ee') },
  { id: '3', name: 'data.csv', type: 'text/csv', size: 48_000, progress: 62 },
  { id: '4', name: 'huge-video.mov', type: 'video/quicktime', error: 'Too large (max 50 MB)' },
]

function Demo({ variant }: { variant: 'chips' | 'tiles' }) {
  const [items, setItems] = useState(initial)
  return <ChatAttachments className="w-[30rem]" variant={variant} items={items} onRemove={(id) => setItems(items.filter((i) => i.id !== id))} />
}

export const styles = { Chips: () => <Demo variant="chips" />, Tiles: () => <Demo variant="tiles" /> }

Source: packages/ui/src/chat-attachments/chat-attachments.tsx

Catalog

Category
ai-agents
Priority
Pro
Primitive
composed
React Aria
DropZone + TagGroup