Attachment
Preview and download files shared in a conversation. Open the sample attachment, or add your own files and send them with the composer. Files stay in memory and are cleared when this page reloads.
View code
import { useEffect, useRef, useState } from "react";import { Attachment } from "@stack/ui/attachment";import { Composer } from "@stack/ui/composer";import { Message } from "@stack/ui/message";import { Thread } from "@stack/ui/thread";
type AttachedFile = { id: string; src: string; name: string; type: string;};
const sample: AttachedFile = { id: "sample", src: "data:text/plain,Example%20attachment%0AReady%20for%20review.", name: "notes.txt", type: "text/plain",};
function FilePreview({ file, onRemove,}: { file: AttachedFile; onRemove?: () => void;}) { return ( <Attachment.Root src={file.src} name={file.name} type={file.type} onRemove={onRemove} > <Attachment.PreviewTrigger /> <Attachment.Remove /> <Attachment.PreviewContent> <Attachment.Header> <Attachment.HeaderContent> <Attachment.Title /> <Attachment.Description /> </Attachment.HeaderContent> <Attachment.Actions> <Attachment.Download /> <Attachment.Close /> </Attachment.Actions> </Attachment.Header> <Attachment.Content /> </Attachment.PreviewContent> </Attachment.Root> );}
export default function Example() { const [draft, setDraft] = useState<AttachedFile[]>([]); const [messages, setMessages] = useState([ { id: "sample-message", files: [sample] }, ]); const urls = useRef(new Set<string>()); useEffect(() => { const created = urls.current; return () => { for (const url of created) URL.revokeObjectURL(url); created.clear(); }; }, []);
function attach(files: File[]) { const added = files.map((file) => { const src = URL.createObjectURL(file); urls.current.add(src); return { id: crypto.randomUUID(), src, name: file.name, type: file.type }; }); setDraft((current) => [...current, ...added]); }
function submit() { if (!draft.length) return; setMessages((current) => [ ...current, { id: crypto.randomUUID(), files: draft }, ]); setDraft([]); }
return ( <div className="h-96"> <Thread.Root> <Thread.Scroller> <Thread.Messages> <Thread.Content> <Thread.Item messageId="prompt"> <Message.Root> <Message.Content> <Message.Text parts={[ { type: "text", text: "Attach a file to share it in this conversation.", }, ]} /> </Message.Content> </Message.Root> </Thread.Item> {messages.map((message) => ( <Thread.Item key={message.id} messageId={message.id}> <Message.Root align="end"> <Message.Content> <div className="flex flex-wrap justify-end gap-2"> {message.files.map((file) => ( <FilePreview key={file.id} file={file} /> ))} </div> </Message.Content> </Message.Root> </Thread.Item> ))} </Thread.Content> </Thread.Messages> <Thread.ScrollButton /> </Thread.Scroller> <Thread.Footer> <div className="grid gap-3 pt-4"> {!!draft.length && ( <div aria-label="Draft attachments" className="flex flex-wrap gap-2" > {draft.map((file) => ( <FilePreview key={file.id} file={file} onRemove={() => setDraft((current) => current.filter((item) => item.id !== file.id), ) } /> ))} </div> )} <Composer.Root value="" onChange={() => {}} onSubmit={submit} onFiles={attach} submitState={draft.length ? "ready" : "blocked"} > <Composer.AttachMenu onFiles={attach}> <Composer.AttachFiles /> </Composer.AttachMenu> <Composer.Footer> <Composer.Submit label="Send attachments" /> </Composer.Footer> </Composer.Root> </div> </Thread.Footer> </Thread.Root> </div> );}import { Attachment } from "@stack/ui/attachment";Pass a URL, filename, and MIME type. The host application owns URL creation and cleanup for local files.
Reference
Section titled “Reference”Tables list component-specific props and inherited control options. Components that forward native props also accept the applicable React HTML attributes and event handlers, including data-* and aria-* attributes.
Attachment.Root
Section titled “Attachment.Root”Also accepts native element props.
| Prop | Type | Required | Description |
|---|---|---|---|
className |
string |
No | Additional CSS classes for the underlying element. |
children |
ReactNode |
No | Content rendered inside the component. |
disabled |
boolean |
No | Disable interaction. |
name |
string |
Yes | Filename used for display and file-kind detection. |
type |
string |
No | File MIME type. |
src |
string |
Yes | URL of the file or media. |
onRemove |
(() => void) |
No | Remove this attachment from the host’s state. |
Attachment.Provider
Section titled “Attachment.Provider”| Prop | Type | Required | Description |
|---|---|---|---|
children |
ReactNode |
Yes | Content rendered inside the component. |
src |
string |
Yes | URL of the file or media. |
name |
string |
Yes | Filename used for display and file-kind detection. |
type |
string |
No | File MIME type. Default: "". |
onRemove |
(() => void) |
No | Remove this attachment from the host’s state. |
disabled |
boolean |
No | Disable interaction. Default: false. |
Attachment.PreviewTrigger
Section titled “Attachment.PreviewTrigger”Also accepts native element props.
| Prop | Type | Required | Description |
|---|---|---|---|
handle |
Handle<unknown> |
No | A handle to associate the trigger with a dialog. |
payload |
unknown |
No | A payload to pass to the dialog when it is opened. |
id |
string |
No | Element identifier. |
nativeButton |
boolean |
No | Whether the component renders a native <button> element when replacing it via the render prop. |
children |
ReactNode |
No | Content rendered inside the component. |
className |
string | ((state: DialogTriggerState) => string | undefined) |
No | Additional CSS classes for the underlying element. |
render |
ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogTriggerState> |
No | Replace the underlying element using render composition. |
style |
CSSProperties | ((state: DialogTriggerState) => CSSProperties | undefined) |
No | Inline styles for the underlying element. |
Attachment.Thumbnail
Section titled “Attachment.Thumbnail”This component takes no props.
Attachment.PreviewContent
Section titled “Attachment.PreviewContent”Also accepts native element props.
| Prop | Type | Required | Description |
|---|---|---|---|
style |
CSSProperties | ((state: AlertDialogPopupState) => CSSProperties | undefined) |
No | Inline styles for the underlying element. |
className |
string | ((state: AlertDialogPopupState) => string | undefined) |
No | Additional CSS classes for the underlying element. |
children |
ReactNode |
No | Content rendered inside the component. |
initialFocus |
boolean | RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | HTMLElement | null | void) |
No | Determines the element to focus when the dialog is opened. |
finalFocus |
boolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | HTMLElement | null | void) |
No | Determines the element to focus when the dialog is closed. |
render |
ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, AlertDialogPopupState> |
No | Replace the underlying element using render composition. |
Attachment.Header
Section titled “Attachment.Header”Also accepts native element props.
| Prop | Type | Required | Description |
|---|---|---|---|
className |
string |
No | Additional CSS classes for the underlying element. |
children |
ReactNode |
No | Content rendered inside the component. |
size |
"default" | "sm" |
No | Size variant. Default: "default". |
Attachment.HeaderContent
Section titled “Attachment.HeaderContent”Also accepts native element props.
| Prop | Type | Required | Description |
|---|---|---|---|
className |
string |
No | Additional CSS classes for the underlying element. |
children |
ReactNode |
No | Content rendered inside the component. |
Attachment.Actions
Section titled “Attachment.Actions”Also accepts native element props.
| Prop | Type | Required | Description |
|---|---|---|---|
className |
string |
No | Additional CSS classes for the underlying element. |
children |
ReactNode |
No | Content rendered inside the component. |
Attachment.Title
Section titled “Attachment.Title”Also accepts native element props.
| Prop | Type | Required | Description |
|---|---|---|---|
children |
ReactNode |
No | Content rendered inside the component. |
className |
string | ((state: AlertDialogTitleState) => string | undefined) |
No | Additional CSS classes for the underlying element. |
render |
ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, AlertDialogTitleState> |
No | Replace the underlying element using render composition. |
style |
CSSProperties | ((state: AlertDialogTitleState) => CSSProperties | undefined) |
No | Inline styles for the underlying element. |
Attachment.Description
Section titled “Attachment.Description”Also accepts native element props.
| Prop | Type | Required | Description |
|---|---|---|---|
children |
ReactNode |
No | Content rendered inside the component. Default: "Attachment preview". |
className |
string | ((state: AlertDialogDescriptionState) => string | undefined) |
No | Additional CSS classes for the underlying element. |
render |
ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, AlertDialogDescriptionState> |
No | Replace the underlying element using render composition. |
style |
CSSProperties | ((state: AlertDialogDescriptionState) => CSSProperties | undefined) |
No | Inline styles for the underlying element. |
Attachment.Content
Section titled “Attachment.Content”This component takes no props.
Attachment.Download
Section titled “Attachment.Download”Also accepts native element props.
| Prop | Type | Required | Description |
|---|---|---|---|
style |
CSSProperties | ((state: ButtonState) => CSSProperties | undefined) |
No | Inline styles for the underlying element. |
className |
string | ((state: ButtonState) => string | undefined) |
No | Additional CSS classes for the underlying element. |
variant |
"link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null |
No | Appearance variant. |
size |
"default" | "sm" | "xs" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null |
No | Size variant. |
focusableWhenDisabled |
boolean |
No | Whether the button should be focusable when disabled. |
Attachment.Close
Section titled “Attachment.Close”Also accepts native element props.
| Prop | Type | Required | Description |
|---|---|---|---|
style |
CSSProperties | ((state: AlertDialogCloseState) => CSSProperties | undefined) |
No | Inline styles for the underlying element. |
className |
string | ((state: AlertDialogCloseState) => string | undefined) |
No | Additional CSS classes for the underlying element. |
nativeButton |
boolean |
No | Whether the component renders a native <button> element when replacing it via the render prop. |
Attachment.Remove
Section titled “Attachment.Remove”Also accepts native element props.
| Prop | Type | Required | Description |
|---|---|---|---|
style |
CSSProperties | ((state: ButtonState) => CSSProperties | undefined) |
No | Inline styles for the underlying element. |
className |
string | ((state: ButtonState) => string | undefined) |
No | Additional CSS classes for the underlying element. |
variant |
"link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null |
No | Appearance variant. |
size |
"default" | "sm" | "xs" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null |
No | Size variant. |
render |
ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ButtonState> |
No | Replace the underlying element using render composition. |
focusableWhenDisabled |
boolean |
No | Whether the button should be focusable when disabled. |
nativeButton |
boolean |
No | Whether the component renders a native <button> element when replacing it via the render prop. |