Show files in composers, threads, and upload lists. Each attachment can combine a preview, metadata, upload state, and actions.
registry-index.json
Uploading · 58%
accordion.tsx
TSX · 6.2 KB
1 import { 2 Attachment , 3 AttachmentAction , 4 AttachmentActions , 5 AttachmentContent , 6 AttachmentDescription , 7 AttachmentGroup , 8 AttachmentMedia , 9 AttachmentTitle , 10 } from " @/components/ui/attachment " 11 12 export function Example ( ) { 13 return ( 14 < div className = " flex w-full flex-col gap-3 " > 15 < AttachmentGroup className = " w-full sm:grid sm:grid-cols-3 sm:overflow-visible " > 16 < Attachment orientation = " vertical " className = " w-52 shrink-0 snap-start snap-always sm:w-full sm:snap-align-none " > 17 < AttachmentMedia variant = " image " > 18 < img src = " /assets/brand/demos/attachments/attachment-1.png " alt = " " /> 19 </ AttachmentMedia > 20 < AttachmentContent > 21 < AttachmentTitle > lake-study.png </ AttachmentTitle > 22 < AttachmentDescription > PNG · 820 KB </ AttachmentDescription > 23 </ AttachmentContent > 24 </ Attachment > 25 { } 26 </ AttachmentGroup > 27 < Attachment state = " uploading " > 28 < AttachmentMedia > 29 < FileTextIcon /> 30 </ AttachmentMedia > 31 < AttachmentContent > 32 < AttachmentTitle > registry-index.json </ AttachmentTitle > 33 < AttachmentDescription > Uploading · 58% </ AttachmentDescription > 34 </ AttachmentContent > 35 < AttachmentActions > 36 < AttachmentAction aria- label = " Remove registry-index.json " > 37 < XIcon /> 38 </ AttachmentAction > 39 </ AttachmentActions > 40 </ Attachment > 41 < Attachment > 42 < AttachmentMedia > 43 < FileCodeIcon /> 44 </ AttachmentMedia > 45 < AttachmentContent > 46 < AttachmentTitle > accordion.tsx </ AttachmentTitle > 47 < AttachmentDescription > TSX · 6.2 KB </ AttachmentDescription > 48 </ AttachmentContent > 49 < AttachmentActions > 50 < AttachmentAction aria- label = " Remove accordion.tsx " > 51 < XIcon /> 52 </ AttachmentAction > 53 </ AttachmentActions > 54 </ Attachment > 55 </ div > 56 ) 57 }
Installation
npx @arctis-sh/@arctis-sh/ui@latest add attachment
Usage
1 import { 2 Attachment , 3 AttachmentAction , 4 AttachmentActions , 5 AttachmentContent , 6 AttachmentDescription , 7 AttachmentMedia , 8 AttachmentTitle , 9 } from " @/components/ui/attachment "
1 < Attachment > 2 < AttachmentMedia > 3 < FileTextIcon /> 4 </ AttachmentMedia > 5 < AttachmentContent > 6 < AttachmentTitle > registry-index.json </ AttachmentTitle > 7 < AttachmentDescription > JSON · 48 KB </ AttachmentDescription > 8 </ AttachmentContent > 9 < AttachmentActions > 10 < AttachmentAction aria- label = " Remove registry-index.json " > 11 < XIcon /> 12 </ AttachmentAction > 13 </ AttachmentActions > 14 </ Attachment >
Composition
Attachment
├── AttachmentMedia
├── AttachmentContent
│ ├── AttachmentTitle
│ └── AttachmentDescription
├── AttachmentActions
│ └── AttachmentAction
└── AttachmentTrigger
AttachmentGroup
├── Attachment
└── Attachment
Basic
Mix image previews and compact file attachments. Set state="uploading" while a file is in progress.
registry-index.json
Uploading · 58%
accordion.tsx
TSX · 6.2 KB
1 import { 2 Attachment , 3 AttachmentAction , 4 AttachmentActions , 5 AttachmentContent , 6 AttachmentDescription , 7 AttachmentGroup , 8 AttachmentMedia , 9 AttachmentTitle , 10 } from " @/components/ui/attachment " 11 12 export function Example ( ) { 13 return ( 14 < div className = " flex w-full flex-col gap-3 " > 15 < AttachmentGroup className = " w-full sm:grid sm:grid-cols-3 sm:overflow-visible " > 16 < Attachment orientation = " vertical " className = " w-52 shrink-0 snap-start snap-always sm:w-full sm:snap-align-none " > 17 < AttachmentMedia variant = " image " > 18 < img src = " /assets/brand/demos/attachments/attachment-1.png " alt = " " /> 19 </ AttachmentMedia > 20 < AttachmentContent > 21 < AttachmentTitle > lake-study.png </ AttachmentTitle > 22 < AttachmentDescription > PNG · 820 KB </ AttachmentDescription > 23 </ AttachmentContent > 24 </ Attachment > 25 { } 26 </ AttachmentGroup > 27 < Attachment state = " uploading " > 28 < AttachmentMedia > 29 < FileTextIcon /> 30 </ AttachmentMedia > 31 < AttachmentContent > 32 < AttachmentTitle > registry-index.json </ AttachmentTitle > 33 < AttachmentDescription > Uploading · 58% </ AttachmentDescription > 34 </ AttachmentContent > 35 < AttachmentActions > 36 < AttachmentAction aria- label = " Remove registry-index.json " > 37 < XIcon /> 38 </ AttachmentAction > 39 </ AttachmentActions > 40 </ Attachment > 41 < Attachment > 42 < AttachmentMedia > 43 < FileCodeIcon /> 44 </ AttachmentMedia > 45 < AttachmentContent > 46 < AttachmentTitle > accordion.tsx </ AttachmentTitle > 47 < AttachmentDescription > TSX · 6.2 KB </ AttachmentDescription > 48 </ AttachmentContent > 49 < AttachmentActions > 50 < AttachmentAction aria- label = " Remove accordion.tsx " > 51 < XIcon /> 52 </ AttachmentAction > 53 </ AttachmentActions > 54 </ Attachment > 55 </ div > 56 ) 57 }
Image
Set variant="image" on AttachmentMedia and orientation="vertical" to place a preview above the file name and metadata.
1 import { 2 Attachment , 3 AttachmentContent , 4 AttachmentDescription , 5 AttachmentGroup , 6 AttachmentMedia , 7 AttachmentTitle , 8 } from " @/components/ui/attachment " 9 10 export function Example ( ) { 11 return ( 12 < AttachmentGroup className = " w-full sm:grid sm:grid-cols-3 sm:overflow-visible " > 13 < Attachment orientation = " vertical " className = " w-52 shrink-0 snap-start snap-always sm:w-full sm:snap-align-none " > 14 < AttachmentMedia variant = " image " > 15 < img src = " /assets/brand/demos/attachments/attachment-1.png " alt = " " /> 16 </ AttachmentMedia > 17 < AttachmentContent > 18 < AttachmentTitle > lake-study.png </ AttachmentTitle > 19 < AttachmentDescription > PNG · 820 KB </ AttachmentDescription > 20 </ AttachmentContent > 21 </ Attachment > 22 </ AttachmentGroup > 23 ) 24 }
States
state controls the visual treatment. uploading and processing dim the media and show progress, while error uses a destructive surface.
theme-tokens.css
Queued · waiting to upload
docs-export.md
Uploading · 58%
component-catalog.json
Indexing for the registry
og-cover.png
Upload failed · try again
alert-dialog.tsx
Uploaded · 9.4 KB
1 import { 2 Attachment , 3 AttachmentAction , 4 AttachmentActions , 5 AttachmentContent , 6 AttachmentDescription , 7 AttachmentMedia , 8 AttachmentTitle , 9 } from " @/components/ui/attachment " 10 11 export function Example ( ) { 12 return ( 13 < div className = " flex flex-col gap-3 " > 14 < Attachment state = " idle " > 15 < AttachmentMedia > 16 < ClockIcon /> 17 </ AttachmentMedia > 18 < AttachmentContent > 19 < AttachmentTitle > theme-tokens.css </ AttachmentTitle > 20 < AttachmentDescription > Queued · waiting to upload </ AttachmentDescription > 21 </ AttachmentContent > 22 < AttachmentActions > 23 < AttachmentAction aria- label = " Remove theme-tokens.css " > 24 < XIcon /> 25 </ AttachmentAction > 26 </ AttachmentActions > 27 </ Attachment > 28 < Attachment state = " uploading " > 29 < AttachmentMedia > 30 < FileTextIcon /> 31 </ AttachmentMedia > 32 < AttachmentContent > 33 < AttachmentTitle > docs-export.md </ AttachmentTitle > 34 < AttachmentDescription > Uploading · 58% </ AttachmentDescription > 35 </ AttachmentContent > 36 < AttachmentActions > 37 < AttachmentAction aria- label = " Remove docs-export.md " > 38 < XIcon /> 39 </ AttachmentAction > 40 </ AttachmentActions > 41 </ Attachment > 42 < Attachment state = " processing " > 43 < AttachmentMedia > 44 < FileTextIcon /> 45 </ AttachmentMedia > 46 < AttachmentContent > 47 < AttachmentTitle > component-catalog.json </ AttachmentTitle > 48 < AttachmentDescription > Indexing for the registry </ AttachmentDescription > 49 </ AttachmentContent > 50 < AttachmentActions > 51 < AttachmentAction aria- label = " Remove component-catalog.json " > 52 < XIcon /> 53 </ AttachmentAction > 54 </ AttachmentActions > 55 </ Attachment > 56 < Attachment state = " error " > 57 < AttachmentMedia > 58 < AlertIcon /> 59 </ AttachmentMedia > 60 < AttachmentContent > 61 < AttachmentTitle > og-cover.png </ AttachmentTitle > 62 < AttachmentDescription > Upload failed · try again </ AttachmentDescription > 63 </ AttachmentContent > 64 < AttachmentActions > 65 < AttachmentAction aria- label = " Retry og-cover.png " > 66 < RetryIcon /> 67 </ AttachmentAction > 68 < AttachmentAction aria- label = " Remove og-cover.png " > 69 < XIcon /> 70 </ AttachmentAction > 71 </ AttachmentActions > 72 </ Attachment > 73 < Attachment state = " done " > 74 < AttachmentMedia > 75 < CheckIcon /> 76 </ AttachmentMedia > 77 < AttachmentContent > 78 < AttachmentTitle > alert-dialog.tsx </ AttachmentTitle > 79 < AttachmentDescription > Uploaded · 9.4 KB </ AttachmentDescription > 80 </ AttachmentContent > 81 < AttachmentActions > 82 < AttachmentAction aria- label = " Remove alert-dialog.tsx " > 83 < XIcon /> 84 </ AttachmentAction > 85 </ AttachmentActions > 86 </ Attachment > 87 </ div > 88 ) 89 }
Sizes
Choose default, sm, or xs with the size prop.
1 import { 2 Attachment , 3 AttachmentContent , 4 AttachmentDescription , 5 AttachmentMedia , 6 AttachmentTitle , 7 } from " @/components/ui/attachment " 8 9 export function Example ( ) { 10 return ( 11 < div className = " flex flex-col gap-3 " > 12 < Attachment size = " default " > 13 < AttachmentMedia > 14 < FileTextIcon /> 15 </ AttachmentMedia > 16 < AttachmentContent > 17 < AttachmentTitle > Default size </ AttachmentTitle > 18 < AttachmentDescription > MD · 2.1 KB </ AttachmentDescription > 19 </ AttachmentContent > 20 </ Attachment > 21 < Attachment size = " sm " > 22 < AttachmentMedia > 23 < FileTextIcon /> 24 </ AttachmentMedia > 25 < AttachmentContent > 26 < AttachmentTitle > Small size </ AttachmentTitle > 27 < AttachmentDescription > MD · 2.1 KB </ AttachmentDescription > 28 </ AttachmentContent > 29 </ Attachment > 30 < Attachment size = " xs " > 31 < AttachmentMedia > 32 < FileTextIcon /> 33 </ AttachmentMedia > 34 < AttachmentContent > 35 < AttachmentTitle > Extra small </ AttachmentTitle > 36 < AttachmentDescription > MD · 2.1 KB </ AttachmentDescription > 37 </ AttachmentContent > 38 </ Attachment > 39 </ div > 40 ) 41 }
Group
AttachmentGroup arranges items in a horizontal row. Swipe on touch or drag on desktop; the row settles on the nearest card.
1 import { 2 Attachment , 3 AttachmentAction , 4 AttachmentActions , 5 AttachmentContent , 6 AttachmentDescription , 7 AttachmentGroup , 8 AttachmentMedia , 9 AttachmentTitle , 10 } from " @/components/ui/attachment " 11 12 export function Example ( ) { 13 return ( 14 < AttachmentGroup > 15 < Attachment className = " w-64 shrink-0 " > 16 < AttachmentMedia variant = " image " > 17 < img src = " /assets/brand/demos/attachments/attachment-4.png " alt = " " /> 18 </ AttachmentMedia > 19 < AttachmentContent > 20 < AttachmentTitle > dune-pass.png </ AttachmentTitle > 21 < AttachmentDescription > PNG · 640 KB </ AttachmentDescription > 22 </ AttachmentContent > 23 < AttachmentActions > 24 < AttachmentAction aria- label = " Remove dune-pass.png " > 25 < XIcon /> 26 </ AttachmentAction > 27 </ AttachmentActions > 28 </ Attachment > 29 < Attachment className = " w-64 shrink-0 " > 30 < AttachmentMedia > 31 < FileTextIcon /> 32 </ AttachmentMedia > 33 < AttachmentContent > 34 < AttachmentTitle > release-notes.md </ AttachmentTitle > 35 < AttachmentDescription > MD · 12 KB </ AttachmentDescription > 36 </ AttachmentContent > 37 < AttachmentActions > 38 < AttachmentAction aria- label = " Remove release-notes.md " > 39 < XIcon /> 40 </ AttachmentAction > 41 </ AttachmentActions > 42 </ Attachment > 43 < Attachment className = " w-64 shrink-0 " > 44 < AttachmentMedia variant = " image " > 45 < img src = " /assets/brand/demos/attachments/attachment-5.png " alt = " " /> 46 </ AttachmentMedia > 47 < AttachmentContent > 48 < AttachmentTitle > alpine.png </ AttachmentTitle > 49 < AttachmentDescription > PNG · 700 KB </ AttachmentDescription > 50 </ AttachmentContent > 51 < AttachmentActions > 52 < AttachmentAction aria- label = " Remove alpine.png " > 53 < XIcon /> 54 </ AttachmentAction > 55 </ AttachmentActions > 56 </ Attachment > 57 < Attachment className = " w-64 shrink-0 " > 58 < AttachmentMedia > 59 < FileCodeIcon /> 60 </ AttachmentMedia > 61 < AttachmentContent > 62 < AttachmentTitle > button.tsx </ AttachmentTitle > 63 < AttachmentDescription > TSX · 4 KB </ AttachmentDescription > 64 </ AttachmentContent > 65 < AttachmentActions > 66 < AttachmentAction aria- label = " Remove button.tsx " > 67 < XIcon /> 68 </ AttachmentAction > 69 </ AttachmentActions > 70 </ Attachment > 71 </ AttachmentGroup > 72 ) 73 }
Trigger
AttachmentTrigger fills the card, making the whole attachment clickable while keeping its actions available.
changelog.md
Open the full notes
1 import { 2 Attachment , 3 AttachmentAction , 4 AttachmentActions , 5 AttachmentContent , 6 AttachmentDescription , 7 AttachmentMedia , 8 AttachmentTitle , 9 AttachmentTrigger , 10 } from " @/components/ui/attachment " 11 12 export function Example ( ) { 13 return ( 14 < Attachment > 15 < AttachmentMedia > 16 < FileTextIcon /> 17 </ AttachmentMedia > 18 < AttachmentContent > 19 < AttachmentTitle > changelog.md </ AttachmentTitle > 20 < AttachmentDescription > Open the full notes </ AttachmentDescription > 21 </ AttachmentContent > 22 < AttachmentActions > 23 < AttachmentAction aria- label = " Download changelog.md " > 24 < DownloadIcon /> 25 </ AttachmentAction > 26 < AttachmentAction aria- label = " Remove changelog.md " > 27 < XIcon /> 28 </ AttachmentAction > 29 </ AttachmentActions > 30 < AttachmentTrigger aria- label = " Open changelog.md " /> 31 </ Attachment > 32 ) 33 }
API Reference
Attachment
Prop Type Default state "idle" | "uploading" | "processing" | "error" | "done" "done" size "default" | "sm" | "xs" "default" orientation "horizontal" | "vertical" "horizontal" className string —
1 < Attachment > 2 < AttachmentMedia > 3 < Icon /> 4 </ AttachmentMedia > 5 < AttachmentContent > 6 < AttachmentTitle > report.pdf </ AttachmentTitle > 7 < AttachmentDescription > 2.4 MB </ AttachmentDescription > 8 </ AttachmentContent > 9 </ Attachment >
Prop Type Default variant "icon" | "image" "icon" className string —
1 < AttachmentMedia > 2 < Icon /> 3 </ AttachmentMedia >
AttachmentContent
Prop Type Default className string —
1 < AttachmentContent > 2 < AttachmentTitle > report.pdf </ AttachmentTitle > 3 < AttachmentDescription > 2.4 MB </ AttachmentDescription > 4 </ AttachmentContent >
AttachmentTitle
Prop Type Default className string —
1 < AttachmentTitle > report.pdf </ AttachmentTitle >
AttachmentDescription
Prop Type Default className string —
1 < AttachmentDescription > 2.4 MB </ AttachmentDescription >
AttachmentActions
Prop Type Default className string —
1 < AttachmentActions > 2 < AttachmentAction aria- label = " Remove " /> 3 </ AttachmentActions >
AttachmentAction
Prop Type Default className string — aria-label string —
1 < AttachmentAction aria- label = " Download " />
AttachmentTrigger
Prop Type Default className string — aria-label string —
1 < AttachmentTrigger > 2 < Attachment > ... </ Attachment > 3 </ AttachmentTrigger >
AttachmentGroup
Prop Type Default className string —
1 < AttachmentGroup > 2 < Attachment > ... </ Attachment > 3 < Attachment > ... </ Attachment > 4 </ AttachmentGroup >