File Upload
Headless file upload. Combines a hidden native <input type="file"> with a styleable dropzone or trigger button. Validates against accept, maxFiles, and maxSize and reports rejections via onReject.
Features
Trigger and drag-and-drop Dropzone — use either or both.onReject callback reports rejected files with a reason.Items for listing selected files with a per-row remove.Example
Click or drag images here
PNG / JPG · max 2 MB each
Anatomy
import { FileUpload } from '@wire-ui/react'
<FileUpload.Root multiple>
<FileUpload.Input />
<FileUpload.Dropzone>
Click or drag files here
</FileUpload.Dropzone>
<FileUpload.Items>
{(file, i, remove) => (
<li key={i}>
{file.name}
<button onClick={remove}>Remove</button>
</li>
)}
</FileUpload.Items>
</FileUpload.Root>Styling
The Dropzone exposes data-dragging while the user is dragging files over it, and data-disabled when disabled. Use these to give visual feedback during drag-over.
<FileUpload.Dropzone className="
border border-dashed border-black
data-[dragging]:bg-[#e5e5e5] data-[dragging]:border-solid
data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed
" />Using data attributes
[data-dragging] { background: #e5e5e5; border-style: solid; }
[data-disabled] { opacity: 0.5; cursor: not-allowed; }Trigger vs Dropzone
Both open the file picker. Use Trigger for a button-style flow (“Choose files”) and Dropzone for drag-and-drop with click fallback. They can coexist within the same Root.
Validation and rejections
Files that fail accept, maxSize, or maxFiles are passed to onReject with a reason. Accepted files are appended to the list (single-file uploaders replace the existing file).
<FileUpload.Root
accept=".pdf,.doc,.docx"
maxFiles={3}
maxSize={5 * 1024 * 1024}
multiple
onReject={(rejected) => {
for (const { file, reason } of rejected) {
// reason is 'maxFiles' | 'maxSize' | 'accept'
console.warn(`${file.name} rejected: ${reason}`)
}
}}
/>Root props
| Prop | Type | Default | Description |
|---|---|---|---|
value | File[] | — | Controlled file list |
defaultValue | File[] | [] | Initial file list |
onChange | (files: File[]) => void | — | Called when the list changes |
multiple | boolean | false | Allow selecting multiple files |
accept | string | — | Comma-separated MIME types or extensions (e.g. image/* or .pdf,.doc) |
maxFiles | number | — | Maximum number of files allowed |
maxSize | number | — | Maximum size per file in bytes |
disabled | boolean | false | Disables the trigger + dropzone |
onReject | (rejected: { file: File; reason: 'maxFiles' | 'maxSize' | 'accept' }[]) => void | — | Called when files are rejected |
Items render-prop
FileUpload.Items takes a single function child: (file, index, remove) => ReactNode. Calling remove() deletes that file from the list.
Data attributes
| Attribute | Element | When present |
|---|---|---|
data-dragging | Dropzone | A drag-over is in progress |
data-disabled | Root, Dropzone, Trigger | disabled prop is true |
Accessibility
- The native
<input type="file">stays in the DOM (visually hidden) so screen readers and form submission work normally. Dropzoneisrole="button"withtabIndex={0}so it’s keyboard-accessible.- Pressing Enter or Space on the focused Dropzone opens the file picker.
Keyboard Interactions
| Key | Description |
|---|---|
| Tab | Moves focus to the Trigger or Dropzone. |
| Enter / Space | Opens the native file picker. |