React UI
Composer attachments
Add file picker, direct input, dropzone, and controlled attachments to @anvia/react-ui composer surfaces.
Use Composer.Attachments for pending files before submit. It can render the default attachment
row or a design-system row with Attachment primitives.
For the full route-boundary flow, including how files become core messages, see Attachments end to end.
Attachment list
import { Attachment, Composer } from "@anvia/react-ui";
export function AttachmentComposer() {
return (
<Composer.Root className="composer">
<Composer.Attachments className="attachment-list">
{(attachment) => (
<Attachment.Root className="attachment-row">
<Attachment.Preview />
<Attachment.Name />
<Attachment.Remove>Remove</Attachment.Remove>
</Attachment.Root>
)}
</Composer.Attachments>
<Composer.AddAttachment accept="image/*,.pdf" multiple>
Attach
</Composer.AddAttachment>
<Composer.Input />
<Composer.Submit />
</Composer.Root>
);
}.attachment-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.attachment-row {
display: inline-flex;
max-width: 240px;
align-items: center;
gap: 8px;
border: 1px solid #3f3f46;
border-radius: 10px;
padding: 6px 8px;
}
.attachment-row [data-anvia-attachment-name] {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}import { Attachment, Composer } from "@anvia/react-ui";
export function AttachmentComposer() {
return (
<Composer.Root className="composer">
<Composer.Attachments className="flex flex-wrap gap-2">
{(attachment) => (
<Attachment.Root className="inline-flex max-w-60 items-center gap-2 rounded-lg border border-zinc-700 px-2 py-1.5">
<Attachment.Preview />
<Attachment.Name className="min-w-0 truncate" />
<Attachment.Remove>Remove</Attachment.Remove>
</Attachment.Root>
)}
</Composer.Attachments>
<Composer.AddAttachment accept="image/*,.pdf" multiple>
Attach
</Composer.AddAttachment>
<Composer.Input />
<Composer.Submit />
</Composer.Root>
);
}Dropzone
Use Composer.AttachmentDropzone when the whole composer or an inner region should accept dropped
files. It emits data-dragging while a valid drag is over the zone.
import { Composer } from "@anvia/react-ui";
export function DropzoneComposer() {
return (
<Composer.Root className="composer">
<Composer.AttachmentDropzone className="dropzone">
<Composer.Attachments className="attachment-list" />
<Composer.AddAttachment accept="image/*,.pdf" multiple>
Attach image or PDF
</Composer.AddAttachment>
<Composer.Input />
<Composer.Submit />
</Composer.AttachmentDropzone>
</Composer.Root>
);
}.dropzone {
display: grid;
gap: 10px;
border: 1px dashed #52525b;
border-radius: 14px;
padding: 12px;
}
.dropzone[data-dragging] {
border-color: #2bf563;
background: rgba(43, 245, 99, 0.08);
}import { Composer } from "@anvia/react-ui";
export function DropzoneComposer() {
return (
<Composer.Root className="composer">
<Composer.AttachmentDropzone className="grid gap-2.5 rounded-xl border border-dashed border-zinc-600 p-3 data-[dragging]:border-[#2BF563] data-[dragging]:bg-[#2BF563]/10">
<Composer.Attachments className="flex flex-wrap gap-2" />
<Composer.AddAttachment accept="image/*,.pdf" multiple>
Attach image or PDF
</Composer.AddAttachment>
<Composer.Input />
<Composer.Submit />
</Composer.AttachmentDropzone>
</Composer.Root>
);
}Direct input trigger
Use Composer.AttachmentInput when an app-owned label or button should trigger the underlying file
input.
<label className="file-trigger">
Upload files
<Composer.AttachmentInput className="sr-only" accept="image/*,.pdf" multiple />
</label>
Controlled attachments
Use controlled props when pending attachments must be mirrored into local form state, stored before submit, or validated by application code.
const [attachments, setAttachments] = useState<UIAttachment[]>([]);
return (
<Composer.Root
className="composer"
attachments={attachments}
onAttachmentsChange={setAttachments}
>
<Composer.Attachments keepMounted className="attachment-list" />
<Composer.AttachmentInput multiple />
<Composer.Input />
<Composer.Submit />
</Composer.Root>
);