FileUpload
A file upload is a drop area with a hidden <input type="file"> behind a
Button. The button is the accessible control; the whole drop area 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/primitives/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 drop area, and passed to
onError; the drop area 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. buttonLabel, dropLabel,
description and icon replace the built-in copy; icon={null} hides
the glyph and showFileList={false} hides the list.
In a Field
Inside a Field the trigger takes the field's control id, so the
Label focuses it, and the field's FieldDescription / FieldError join
the component's own description and error in the trigger's
aria-describedby. disabled and invalid inherit from the field;
name puts the shown list on the native input so a native form submit
sends it.
Props
FileUploadProps extends HTMLAttributes<HTMLDivElement> (minus
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 |
onValueChange | (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 | Field disabled | |
invalid | boolean | Field invalid | aria-invalid on the trigger, data-invalid on the drop area. A rejected selection sets it too |
name | string | — | Name of the native input; its file list mirrors the shown list |
id | string | Field control id | Goes on the trigger button (what a Label htmlFor focuses) |
buttonLabel | ReactNode | 'Choose file' / 'Choose files' | |
dropLabel | 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 |
showFileList | 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 drop area and passed to onError |
statusLabel | (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 |
dropAreaClassName | string | — | Merged last onto the drop area |
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-invalid, data-disabled |
[data-slot="file-upload-drop-area"] | div | Click and drop target. 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 FieldError 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 drop-area border while dragging */
[data-slot="file-upload-drop-area"][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 | Drop-area border, surface and hover / dragging fill |
--primary | Drop-area border while dragging |
--danger | Invalid border and the error text |
--card, --card-foreground, --border | File list items |
--muted-foreground | Icons, drop text, description, sizes |
--radius-lg, --radius-md | Drop-area 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
Fieldthe trigger takes the field's control id andaria-labelledby(the field'sLabel, then the button's own text, so the name reads "Attachments Choose files"); the field'sFieldDescription/FieldError, the component's description and any error joinaria-describedby.aria-invalidfollows the field. - 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 drop area and disables the buttons.- Drag and drop is an enhancement; every file can be added through the button.
Related
- Field: label, description and error wiring.
- Button: the trigger and remove buttons.
- FieldError: the rejection text.