Attachment
Installation
npm install @radix-ui/react-progress@^1.1.8 @radix-ui/react-separator@^1.1.8 @radix-ui/react-slot@^1.2.4 class-variance-authority@^0.7.1 clsx@^2.1.1 tailwind-merge@^3.4.0components/item/index.ts
components/item/item.tsx
components/progress/index.ts
components/progress/progress.css
components/progress/progress.tsx
components/separator/index.ts
components/separator/separator.tsx
lib/utils.ts
components/attachment/attachment.tsx
components/attachment/index.ts/* app/globals.css */
@import '../components/progress/progress.css';The code below also uses:
npm install @tabler/icons-react@^3.36.1components/button-group/button-group-context.ts
components/spinner/index.ts
components/spinner/spinner.tsx
lib/icons.ts
components/button/button.tsx
components/button/index.tsCode
'use client';
import { IconFileTypePdf, IconX } from '@tabler/icons-react';
import {
Attachment,
AttachmentActions,
AttachmentContent,
AttachmentDescription,
AttachmentMedia,
AttachmentTitle,
} from '@/components/attachment/attachment';
import { Button } from '@/components/button/button';
export default function AttachmentDemo() {
return (
<Attachment className="nx:w-80">
<AttachmentMedia variant="icon">
<IconFileTypePdf />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>report.pdf</AttachmentTitle>
<AttachmentDescription>2.4 MB</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<Button variant="ghost" size="icon-sm" aria-label="Remove report.pdf">
<IconX />
</Button>
</AttachmentActions>
</Attachment>
);
}
Props
Attachment
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | "horizontal" | "vertical" | null | "horizontal" | No description |
size | "default" | "sm" | null | "default" | No description |
state | AttachmentState | "done" | The attachment's lifecycle state. Drives the border treatment (dashed while |
statusLabel | string | No default documented | Overrides the assistive-tech status text for the current |
AttachmentActions
AttachmentActions adds no props of its own; it accepts the props of the element or primitive it wraps.
AttachmentContent
AttachmentContent adds no props of its own; it accepts the props of the element or primitive it wraps.
AttachmentDescription
AttachmentDescription adds no props of its own; it accepts the props of the element or primitive it wraps.
AttachmentGroup
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label | string | No default documented | No description |
aria-labelledby | string | No default documented | No description |
AttachmentMedia
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "icon" | "image" | null | "default" | No description |
AttachmentProgress
AttachmentProgress adds no props of its own; it accepts the props of the element or primitive it wraps.
AttachmentTitle
| Prop | Type | Default | Description |
|---|---|---|---|
trailing | ReactNode | No default documented | A badge or status icon pinned after the file name. It keeps |
AttachmentTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render as the child element via Radix Slot — e.g. an |
Examples
States
'use client';
import {
IconAlertCircle,
IconFileTypePdf,
IconFileTypeTxt,
IconRefresh,
IconUpload,
IconX,
} from '@tabler/icons-react';
import {
Attachment,
AttachmentActions,
AttachmentContent,
AttachmentDescription,
AttachmentMedia,
AttachmentProgress,
AttachmentTitle,
} from '@/components/attachment/attachment';
import { Button } from '@/components/button/button';
import { Spinner } from '@/components/spinner/spinner';
export default function AttachmentStates() {
return (
<div className="nx:flex nx:w-80 nx:flex-col nx:gap-3">
<Attachment state="idle">
<AttachmentMedia variant="icon">
<IconUpload />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>Drop a file here</AttachmentTitle>
<AttachmentDescription>PDF or PNG, up to 10 MB</AttachmentDescription>
</AttachmentContent>
</Attachment>
<Attachment state="uploading">
<AttachmentMedia variant="icon">
<IconFileTypePdf />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>report.pdf</AttachmentTitle>
<AttachmentDescription>62% of 2.4 MB</AttachmentDescription>
<AttachmentProgress value={62} aria-label="Uploading report.pdf" />
</AttachmentContent>
<AttachmentActions>
<Button
variant="ghost"
size="icon-sm"
aria-label="Cancel upload of report.pdf"
>
<IconX />
</Button>
</AttachmentActions>
</Attachment>
<Attachment state="processing">
<AttachmentMedia variant="icon">
<Spinner aria-hidden="true" />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>scan.png</AttachmentTitle>
<AttachmentDescription>Processing…</AttachmentDescription>
</AttachmentContent>
</Attachment>
<Attachment state="error">
<AttachmentMedia variant="icon">
<IconAlertCircle />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>archive.zip</AttachmentTitle>
<AttachmentDescription>Upload failed</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<Button
variant="ghost"
size="icon-sm"
aria-label="Retry upload of archive.zip"
>
<IconRefresh />
</Button>
</AttachmentActions>
</Attachment>
<Attachment state="done">
<AttachmentMedia variant="icon">
<IconFileTypeTxt />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>notes.txt</AttachmentTitle>
<AttachmentDescription>8 KB</AttachmentDescription>
</AttachmentContent>
</Attachment>
</div>
);
}
Sizes
'use client';
import { IconFileTypePdf, IconFileTypeTxt } from '@tabler/icons-react';
import {
Attachment,
AttachmentContent,
AttachmentDescription,
AttachmentMedia,
AttachmentTitle,
} from '@/components/attachment/attachment';
export default function AttachmentSizes() {
return (
<div className="nx:flex nx:w-80 nx:flex-col nx:gap-3">
<Attachment size="default">
<AttachmentMedia variant="icon">
<IconFileTypePdf />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>report.pdf</AttachmentTitle>
<AttachmentDescription>Default · 2.4 MB</AttachmentDescription>
</AttachmentContent>
</Attachment>
<Attachment size="sm">
<AttachmentMedia variant="icon">
<IconFileTypeTxt />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>notes.txt</AttachmentTitle>
<AttachmentDescription>Small · 8 KB</AttachmentDescription>
</AttachmentContent>
</Attachment>
</div>
);
}
Group
'use client';
import {
IconFileTypeDocx,
IconFileTypePdf,
IconFileTypePng,
IconFileTypeTxt,
IconFileTypeZip,
} from '@tabler/icons-react';
import {
Attachment,
AttachmentContent,
AttachmentDescription,
AttachmentGroup,
AttachmentMedia,
AttachmentTitle,
} from '@/components/attachment/attachment';
const files = [
{ name: 'cover.png', size: '412 KB', icon: <IconFileTypePng /> },
{ name: 'report.pdf', size: '2.4 MB', icon: <IconFileTypePdf /> },
{ name: 'notes.txt', size: '8 KB', icon: <IconFileTypeTxt /> },
{ name: 'archive.zip', size: '18 MB', icon: <IconFileTypeZip /> },
{ name: 'brief.docx', size: '96 KB', icon: <IconFileTypeDocx /> },
];
export default function AttachmentGroupDemo() {
return (
<AttachmentGroup aria-label="Attached files" className="nx:w-80">
{files.map((file) => (
<Attachment key={file.name} orientation="vertical">
<AttachmentMedia variant="icon">{file.icon}</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>{file.name}</AttachmentTitle>
<AttachmentDescription>{file.size}</AttachmentDescription>
</AttachmentContent>
</Attachment>
))}
</AttachmentGroup>
);
}