Skip to main content
NachUI

Attachments

Files hanging off a message or a composer, as a grid, a row of chips or a list.

design-system.pdf243 KB
registry.ts12 KB
notes.md3.0 KB
1'use client';
2
3import { useState } from 'react';
4import { Attachments, type AttachmentData } from '@/components/ui/attachments';
5
6const INITIAL: AttachmentData[] = [
7 { id: '1', filename: 'design-system.pdf', mediaType: 'application/pdf', size: 248320 },
8 { id: '2', filename: 'registry.ts', mediaType: 'text/typescript', size: 12480 },
9 { id: '3', filename: 'notes.md', mediaType: 'text/markdown', size: 3120 },
10];
11
12export function Default() {
13 const [files, setFiles] = useState(INITIAL);
14
15 if (files.length === 0) {
16 return (
17 <div className="w-full max-w-md">
18 <Attachments>
19 <Attachments.Empty>Everything removed, reload the demo to start over</Attachments.Empty>
20 </Attachments>
21 </div>
22 );
23 }
24
25 return (
26 <div className="w-full max-w-md">
27 <Attachments>
28 {files.map((file) => (
29 <Attachments.Item
30 key={file.id}
31 data={file}
32 onRemove={() => setFiles((current) => current.filter((item) => item.id !== file.id))}
33 >
34 <Attachments.Preview />
35 <Attachments.Info />
36 <Attachments.Remove />
37 </Attachments.Item>
38 ))}
39 </Attachments>
40 </div>
41 );
42}

Installation

pnpm dlx nachui add attachments

Anatomy

1import { Attachments } from '@/components/ui/attachments';
1<Attachments variant="grid">
2 {files.map((file) => (
3 <Attachments.Item key={file.id} data={file} onRemove={() => remove(file.id)}>
4 <Attachments.Preview />
5 <Attachments.Info />
6 <Attachments.Remove />
7 </Attachments.Item>
8 ))}
9</Attachments>
Item puts the file in context, so Preview, Info and Remove read it themselves instead of taking the same object three times. An image with a url previews inline; anything else falls back to an icon you can swap.
This is the read side of the story. For the picker and the drop zone see Prompt Input, and for a single file row with upload progress see Attachment.

Composition

Use the following composition to build an Attachments:
Attachments
├── Attachments.Item
│ ├── Attachments.Preview
│ ├── Attachments.Info
│ └── Attachments.Remove
└── Attachments.Empty

Variants

grid wraps cards and hides the remove button until hover, inline is a row of pills that fits above a composer, and list is one full width row per file where the remove button sits at the end.
grid
hero.png180 KB
spec.pdf61 KB
inline
hero.png180 KB
spec.pdf61 KB
list
hero.pngimage/png · 180 KB
spec.pdfapplication/pdf · 61 KB
1'use client';
2
3import { Attachments, type AttachmentData } from '@/components/ui/attachments';
4
5const FILES: AttachmentData[] = [
6 { id: '1', filename: 'hero.png', mediaType: 'image/png', size: 184320 },
7 { id: '2', filename: 'spec.pdf', mediaType: 'application/pdf', size: 62400 },
8];
9
10export function Variants() {
11 return (
12 <div className="flex w-full max-w-md flex-col gap-6">
13 {(['grid', 'inline', 'list'] as const).map((variant) => (
14 <div key={variant} className="flex flex-col gap-2">
15 <span className="text-muted-foreground font-mono text-[11px]">{variant}</span>
16 <Attachments variant={variant}>
17 {FILES.map((file) => (
18 <Attachments.Item key={file.id} data={file}>
19 <Attachments.Preview />
20 <Attachments.Info showMediaType={variant === 'list'} />
21 <Attachments.Remove />
22 </Attachments.Item>
23 ))}
24 </Attachments>
25 </div>
26 ))}
27 </div>
28 );
29}

Data

data is a plain object, not an SDK type:
1interface AttachmentData {
2 id: string;
3 filename?: string;
4 mediaType?: string;
5 url?: string;
6 size?: number;
7}
formatSize and attachmentLabel are exported next to the component, so the same wording shows up wherever you list files.

API Reference

Attachments

PropTypeDefaultDescription
variant'grid' | 'inline' | 'list''grid'Layout of the items
classNamestring-Additional CSS classes

Attachments.Item

PropTypeDefaultDescription
dataAttachmentData-The file this item describes
onRemove() => void-Called by Attachments.Remove
classNamestring-Additional CSS classes

Attachments.Preview

PropTypeDefaultDescription
fallbackIconReactNodefile iconShown when there is no image preview
classNamestring-Additional CSS classes

Attachments.Info

PropTypeDefaultDescription
showMediaTypebooleanfalseAdds the media type to the meta line
classNamestring-Additional CSS classes

Attachments.Remove

PropTypeDefaultDescription
labelstring'Remove'Prefix of the accessible name
classNamestring-Additional CSS classes

Attachments.Empty

PropTypeDefaultDescription
classNamestring-Additional CSS classes
Found something to improve?

Notice a bug, typo, or missing detail on this page? Help us make the documentation better by opening a GitHub issue.

Create an Issue