When to Use
Use FileUpload when users must choose local files and the interface needs a
custom picker, drop target, selected-file list, or client-side feedback. Use a
plain input type="file" when the browser control is sufficient. Client checks
improve feedback but are not a security boundary; validate every uploaded file
again on the server.
Features
The behavior Atom owns before your product adds appearance.
- Supports controlled and uncontrolled file arrays.
- Uses a hidden native file input for picker and form semantics.
- Supports button and drag-and-drop selection.
- Validates accept rules, count, size, and custom errors.
- Appends or replaces multiple-file selections.
- Exposes rejected files, drag state, item metadata, and removal actions.
- Integrates IDs, descriptions, and state with Field.
Import
Anatomy
API Reference
Root
Renders a div, owns selection and validation state, and supplies behavior to
all parts. Local field-state props override Field context.
An uncancelled native form reset restores uncontrolled defaultFiles, clears
rejections and drag state, and resets the native file picker.
files | File[] | - |
defaultFiles | File[] | [] |
onFilesChange | (files: File[]) => void | - |
onRejectedFilesChange | (files: FileUploadRejectedFile[]) => void | - |
accept | string | - |
multiple | boolean | false |
appendFiles | boolean | true when multiple |
maxFiles | number | 1 when single; otherwise - |
maxSize | number | - |
validateFile | (file) => string | null | undefined | false | - |
preventDocumentDrop | boolean | true |
name | string | - |
form | string | - |
disabled | boolean | Field state or false |
required | boolean | Field state or false |
readOnly | boolean | Field state or false |
invalid | boolean | Field state or false |
validationBehavior | "inline" | "native" | Field/Form value or "native" |
asChild | boolean | false |
render | RenderProp | - |
[data-slot] | "file-upload" |
[data-state] | "filled" | "empty" |
[data-drag] | "idle" | "accept" | "reject" |
[data-filled] | Present with selected files |
[data-rejected] | Present with rejected files |
[data-disabled] | Present when disabled |
[data-readonly] | Present when read only |
[data-required] | Present when required |
[data-invalid] | Present when invalid |
Renders the transparent native input type="file", opens through Trigger, and
sends selected FileList values into Root validation. It is aligned with
Trigger so native required feedback points to the visible control and redirects
validation focus there. Accepted drag/drop files are synchronized back to the
native input when the browser permits FileList assignment.
onChange | ChangeEventHandler<HTMLInputElement> | - |
id | Field control ID or generated Root control ID |
name, form, accept, multiple | Root values |
disabled, required | Resolved Root/Field state |
aria-describedby | Explicit Root value or Field message IDs |
aria-invalid | Resolved invalid state |
[data-slot] | "file-upload-hidden-input" |
Trigger
Opens HiddenInput's native file picker. It renders a button by default and adds
button semantics to non-native custom elements. Inside Field, its accessible
name combines the Field label with its authored action text, and it receives
the Field description/error and invalid state. Explicit native ARIA naming and
description props override those defaults.
asChild | boolean | false |
render | RenderProp | - |
role | "button" for non-native custom elements |
aria-disabled | "true" when a custom trigger is disabled or read-only |
aria-labelledby | Field label plus Trigger text when no explicit accessible name is supplied |
aria-describedby | Explicit value or Field description/error IDs |
aria-invalid | "true" when Root or Field is invalid |
[data-slot] | "file-upload-trigger" |
[data-disabled] | Present when Root is disabled |
[data-readonly] | Present when Root is read-only |
[data-required] | Present when Root or Field is required |
[data-invalid] | Present when Root or Field is invalid |
Dropzone
Renders a div that accepts dropped files. It owns drag events but adds no
button role or tab stop; include Trigger when keyboard picker access is needed.
Drag state is validated against the same type, count, size, and custom rules as
the eventual drop. Root prevents file drops on the surrounding document by
default so an accidental outside drop does not replace the current page;
preventDocumentDrop={false} opts out without affecting non-file drag/drop.
asChild | boolean | false |
render | RenderProp | - |
[data-slot] | "file-upload-dropzone" |
[data-drag] | "idle" | "accept" | "reject" |
[data-dragging] | Present for accept/reject drag state |
[data-accepted] | Present for accepted drag state |
[data-rejected] | Present for rejected drag state |
[data-disabled] | Present when disabled |
[data-readonly] | Present when read-only |
ItemGroup
Renders a ul for selected files. A function child receives each (file, index); function children cannot be combined with asChild.
children | ReactNode | ((file, index) => ReactNode) | - |
asChild | boolean | false |
render | RenderProp | - |
[data-slot] | "file-upload-item-group" |
[data-count] | Selected file count |
[data-filled] | Present when count is greater than zero |
Item
Renders one li and provides its resolved File and index to metadata/removal
parts. It returns null when neither file nor the requested Root index exists.
file | File | Root file at index |
index | number | 0 |
asChild | boolean | false |
render | RenderProp | - |
[data-slot] | "file-upload-item" |
[data-index] | Resolved file index |
[data-name] | File name |
[data-size] | File size in bytes |
ItemName
Renders a span containing the File name by default. Consumer children replace
that text; asChild and render are supported.
children | ReactNode | Current file name |
asChild | boolean | false |
render | RenderProp | - |
[data-slot] | "file-upload-item-name" |
ItemSize
Renders a span containing formatFileSize(file.size) by default. Consumer
children replace the formatted text; asChild and render are supported.
children | ReactNode | Formatted current file size |
asChild | boolean | false |
render | RenderProp | - |
[data-slot] | "file-upload-item-size" |
[data-size] | File size in bytes |
ItemDeleteTrigger
Renders a button that removes the current Item, resets the native input so the
same file can be chosen again, and respects disabled/read-only state.
asChild | boolean | false |
render | RenderProp | - |
aria-label | Consumer value or "Remove {file.name}" |
[data-slot] | "file-upload-item-delete-trigger" |
[data-disabled] | Present when disabled |
[data-readonly] | Present when read-only |
fileMatchesAccept
Returns whether a File matches a comma-separated extension, exact MIME type,
or wildcard MIME rule. No rule or an empty rule accepts the file.
validateFileUploadFiles
Returns { acceptedFiles, rejectedFiles }. Rejection errors contain "type",
"size", "count", and any nonempty custom validation string.
Formats bytes with binary-sized B, KB, MB, GB, or TB units and returns "0 B"
for non-finite or nonpositive input.
The entry also exports useFileUploadContext and useFileUploadItemContext for
advanced custom parts; both require their corresponding parent.
Examples
Multiple Documents
Report Rejections
Accessibility
HiddenInput remains an aligned native file control and owns its constraints.
A validation attempt is mirrored to Root, Trigger, and Field. Inline behavior
suppresses the browser bubble; native behavior keeps it at Trigger's bounds.
The visible Trigger carries the Field label, description/error, and invalid
relationships while preserving its own action wording. Drag/drop is optional;
always include a Trigger for keyboard, touch, and single-pointer selection.
HiddenInput supplies native file-input semantics and must be present for
Trigger to open a picker. Give the upload a visible Field.Label or another
accessible name. Trigger and ItemDeleteTrigger use button keyboard behavior;
Dropzone alone is pointer-only. Announce rejection feedback in visible text and
validate file type, size, and content again on the server.
Enter / Space | Opens the picker from Trigger or removes from ItemDeleteTrigger. |
Changelog
Unreleased
0.24.0
- Added source-led Agent Knowledge for native picker and form semantics,
dropzone enhancement, validation boundaries, file-list composition, and
same-file reselection behavior.
0.19.7
- Related the visible Trigger to its Field label, description/error, required,
and invalid state while preserving authored action wording.
- Made Dropzone accept/reject state follow the configured file constraints
before drop and added default file-only document-drop protection.
0.6.16
- Explicitly scrolled inline validation-directed focus into view.
0.6.15
- Exposed inline validation-directed focus through
[data-focus-visible]
until blur.
0.6.13
- Mirrored HiddenInput constraints to the visible upload surface, Field, and
Form under the shared inline/native validation contract.
0.6.12
- Aligned the native file input with Trigger for required feedback and
synchronized accepted files after picker and drag/drop updates.
0.5.0
- Synchronized uncontrolled files, rejection state, and the native picker with
native form reset while preserving Field state and relationships.
0.2.0
- Fixed read-only Trigger, Dropzone, and ItemDeleteTrigger parts so they expose
data-readonly instead of only reporting disabled state.
0.1.0