FileUpload
A file upload is a dropzone with a hidden <input type="file"> behind a
Button. The button is the accessible control; the whole dropzone is also a
click target and accepts drops. Selected files are listed under it with
remove buttons, and a polite live region announces the count after every
change.
import FileUpload from '@zuilib/components/file-upload'
Basic
Single mode replaces the file on every pick. The description under the
trigger is built from accept and maxSize unless you pass one.
Multiple with limits
multiple appends to the list (duplicates by name, size and mtime are
skipped). Files failing accept, maxSize or maxFiles are dropped from
the selection, listed in an error under the dropzone, and passed to
onError; the dropzone turns invalid until the next successful change.
Sizes and text
sm, md and lg scale the trigger button (the shared control sizes),
the padding, the icon and the list text. buttonText, dropText,
description and icon replace the built-in copy; icon={null} hides
the glyph and showList={false} hides the list.
In a FormItem
Inside a FormItem the trigger takes the field's control id, so the
Label focuses it, and the item's Description / Message join the
component's own description and error in the trigger's aria-describedby.
disabled and invalid inherit from the item; name puts the shown list
on the native input so a native form submit sends it.
Props
FileUploadProps extends HTMLAttributes<HTMLDivElement> (minus
onChange, defaultValue, onError) with:
| Prop | Type | Default | Description |
|---|---|---|---|
value | File[] | — | Controlled list. A reset from the parent also clears rejection errors |
defaultValue | File[] | [] | Uncontrolled initial list |
onChange | (files: File[]) => void | — | The full list after every add / remove; rejected files are left out |
onError | (errors: FileUploadError[]) => void | — | Files rejected by accept / maxSize / maxFiles. Each error is { code, file, message } with code 'accept' | 'size' | 'count' |
accept | string | — | Native syntax ("image/*,.pdf"); dropped files are checked against it too |
multiple | boolean | false | Append (deduplicated) instead of replace |
maxSize | number | — | Per-file limit in bytes |
maxFiles | number | — | Total count (multiple only) |
disabled | boolean | FormItem disabled | |
invalid | boolean | FormItem invalid | aria-invalid on the trigger, data-invalid on the dropzone. A rejected selection sets it too |
name | string | — | Name of the native input; its file list mirrors the shown list |
id | string | FormItem control id | Goes on the trigger button (what a Label htmlFor focuses) |
buttonText | ReactNode | 'Choose file' / 'Choose files' | |
dropText | ReactNode | 'or drag and drop' | Text beside the button |
description | ReactNode | from accept / maxSize | Helper line under the trigger |
icon | ReactNode | upload icon | Pass null to hide |
showList | boolean | true | |
formatSize | (bytes: number) => string | formatFileSize | 1536 to 1.5 KB; the default is a named export |
formatError | ({ code, file }) => string | English sentences | Text of a rejection, shown under the dropzone and passed to onError |
statusText | (files: File[]) => ReactNode | 'N files selected' | Content of the polite live region |
removeLabel | (file: File) => string | 'Remove <name>' | Accessible label of each remove button |
size | 'sm' | 'md' | 'lg' | 'md' | |
className | string | — | Merged last onto the root |
dropzoneClassName | string | — | Merged last onto the dropzone |
formatFileSize and the FileUploadError / FileUploadErrorCode types are
named exports of the same module.
Slots
| Slot | Element | Notes |
|---|---|---|
[data-slot="file-upload"] | div | Root. data-size, data-state="default|error", data-invalid, data-disabled |
[data-slot="file-upload-dropzone"] | div | Click and drop target. data-state, data-dragging, data-invalid, data-disabled |
[data-slot="file-upload-input"] | input | The hidden native file input |
[data-slot="file-upload-icon"] | svg | The default upload glyph |
[data-slot="file-upload-actions"] | div | Wraps the trigger and drop text |
[data-slot="file-upload-trigger"] | button | The accessible control (an outline Button) |
[data-slot="file-upload-drop-text"] | span | — |
[data-slot="file-upload-description"] | p | — |
[data-slot="file-upload-error"] | p | A Message with role="alert", present while a rejection stands |
[data-slot="file-upload-status"] | span | Visually hidden polite live region |
[data-slot="file-upload-list"] | ul | — |
[data-slot="file-upload-item"] | li | Parts: file-upload-item-icon, file-upload-item-name, file-upload-item-size, file-upload-item-remove, file-upload-item-remove-icon |
/* Consumer CSS: solid dropzone border while dragging */
[data-slot="file-upload-dropzone"][data-dragging] {
border-style: solid;
}
Tokens
| Token | Used for |
|---|---|
--control-height-sm/md/lg | Trigger height; remove buttons are --control-height-sm squares |
--input, --background, --accent | Dropzone border, surface and hover / dragging fill |
--primary | Dropzone border while dragging |
--destructive | Invalid border and the error text |
--card, --card-foreground, --border | File list items |
--muted-foreground | Icons, drop text, description, sizes |
--radius-lg, --radius-md | Dropzone and item corners |
--duration-fast | Colour transition |
Accessibility
- The trigger is a real
<button>: keyboard users press Enter or Space to open the picker. The native input isaria-hiddenand out of the tab order. - Inside a
FormItemthe trigger takes the Field's control id andaria-labelledby(the item'sLabel, then the button's own text, so the name reads "Attachments Choose files"); the item'sDescription/Message, the component's description and any error joinaria-describedby.aria-invalidfollows the item. - Rejections render in a
role="alert"message; the file count is announced in a politerole="status"region after every add or remove. - Each remove button is labelled
Remove <name>; removing a file moves focus to the next item's remove button, else the previous one, else the trigger, so focus never lands on nothing. disabledblocks picks, drops and removal, setsdata-disabledon the root and dropzone and disables the buttons.- Drag and drop is an enhancement; every file can be added through the button.