File Upload
A keyboard-accessible drop zone and a list of the chosen files, with remove, progress and error states and file validation.
Version 0.4.1 ·
Installation
bunx --bun shadcn@latest add @dga/file-uploadInstalls Field too.
Usage
import { Field, FieldLabel } from "@/components/ui/field";
import { FileUpload } from "@/components/ui/file-upload";
export function DocumentsField() {
return (
<Field name="documents">
<FieldLabel>Supporting documents</FieldLabel>
<FileUpload
accept=".pdf,.jpg,.png"
maxSize={5 * 1024 * 1024}
maxFiles={3}
hint="PDF, JPG or PNG, up to 5 MB each."
onFilesAdded={(files) => files.forEach(upload)}
/>
</Field>
);
}Use File Upload to attach documents and images to a request. The component picks and checks files; you upload them. Pass onFilesAdded to start uploads, then keep the list in your state (files) and update each file's status and progress. Say the accepted formats and the size limit in hint.
Anatomy
- Drop zone (
<label>around a file<input>): the dashed area. The input is the focusable control. - Hint: formats and size limit.
- File list (
<ul>): one row per file with an icon, the name, the size or status, and a remove button. - Progress bar (
role="progressbar") while a file uploads.
Examples
Validation
Files are checked against accept, maxSize and maxFiles. A rejected file stays in the list with the reason, so it is not lost silently. Try a file larger than 5 MB or an .exe file. validateFiles() is exported if you need the same check elsewhere.
States
The list shows a finished file, a file uploading with progress, a rejected file and a file waiting to upload. The drop zone can be disabled or in error.
Reporting progress
Control the list with files and onFilesChange, and update the file while it uploads.
const [files, setFiles] = useState<UploadFile[]>([]);
<FileUpload
files={files}
onFilesChange={setFiles}
onFilesAdded={(added) =>
added.forEach((file) =>
upload(file.file, (progress) =>
setFiles((current) =>
current.map((f) =>
f.id === file.id
? { ...f, status: progress < 100 ? "uploading" : "done", progress }
: f,
),
),
),
)
}
/>RTL
The icon and text of each row start on the right, the remove button is on the left and the progress bar fills from right to left. File names keep their own direction (dir="auto" in a <bdi>): an Arabic name reads right to left and report.pdf left to right. Sizes and percentages are Western digits, left to right.
Accessibility
| Key | Action |
|---|---|
| Tab | Moves focus to the drop zone, then through the remove buttons. |
| Space or Enter | Opens the file picker. |
| Enter or Space on a remove button | Removes that file. |
- The drop zone is a native file input, so it is focusable and works with the keyboard and with assistive technology. Dragging is an extra, never the only way (WCAG 2.1.1).
- The focus outline goes around the whole zone. The dashed border is 3:1 or better; dragging over it adds a solid brand border and a tint.
- Each remove button is named with the file ("Remove report.pdf"). Rejections are announced in a live region and shown in the list with an icon and text.
- A progress bar has
role="progressbar"witharia-valuenow; the percentage is also written next to it. - Translate
messageson Arabic pages.
API Reference
FileUpload
A drop zone and the list of chosen files. The drop zone is a real file input, so it works with the keyboard (Tab, then Space or Enter) and with drag and drop. Files that fail validation stay in the list with their error. Label it with a Field.
| Prop | Type | Default |
|---|---|---|
stringName for form submission. | No default | |
| ||
UploadFile[]The files in the list (controlled). Update `status` and `progress` as uploads run. | No default | |
| ||
stringAccepted formats, as in the HTML `accept` attribute: `.pdf,image/*`. | No default | |
| ||
numberLargest size per file, in bytes. | No default | |
| ||
numberMost files in the list. `multiple` defaults to `true` when this is above 1. | No default | |
| ||
UploadFile[]The files the list starts with (uncontrolled). | [] | |
| ||
(files: UploadFile[]) => voidCalled with the whole list whenever a file is added or removed. | No default | |
| ||
(files: UploadFile[]) => voidCalled with the files that passed validation, so you can start uploading them. | No default | |
| ||
booleanAllows several files. Defaults to `true` unless `maxFiles` is 1. | No default | |
| ||
string | number | bigint | boolean | ReactElement<unknown, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | Promise<AwaitedReactNode>Shows the accepted formats and size under the drop zone. | No default | |
| ||
Partial<FileUploadMessages>Text for the drop zone, upload statuses and errors. Translate it on Arabic pages. | No default | |
| ||
booleanDisables the drop zone. | No default | |
| ||
booleanThe form cannot be submitted without a file. | No default | |
| ||
Also accepts every prop of the Base UI Field (opens in a new tab).
Deviations from DGA
- One component for Figma's Single and Multiple variants, with a drop zone that is a real file input.
- Rejected files stay in the list with the reason.
- Arabic file names keep their own direction.
| Change | Reason |
|---|---|
File Upload: drop zone | A drop zone that only accepts a mouse drag is not keyboard accessible (WCAG 2.1.1). Rejected files stay in the list with the reason. |
File Upload: Arabic file names | A name mixing Arabic and Latin text must not be reordered around its extension. |
Contrast
70 pairs checked · all pass WCAG 2.1 AAShow 23 statesHide
| Variant / state | Light | Dark |
|---|---|---|
| drop zone border | 4.57:1 3:1 | 3.80:1 3:1 |
| drop zone border, hovered | 9.73:1 3:1 | 12.04:1 3:1 |
| drop zone border, drag over | 4.54:1 3:1 | 7.73:1 3:1 |
| drop zone border, error | 4.62:1 3:1 | 4.65:1 3:1 |
| focus ring | 17.31:1 3:1 | 17.49:1 3:1 |
| hint | 4.57:1 AA | 6.32:1 AA |
| drop zone text | 18.09:1 AAA | 15.87:1 AAA |
| drop zone text, hovered | 17.31:1 AAA | 17.04:1 AAA |
| drop zone text, drag over | 17.29:1 AAA | 13.36:1 AAA |
| drop zone icon | 4.74:1 3:1 | 10.38:1 3:1 |
| drop zone icon, drag over | 4.53:1 3:1 | 8.74:1 3:1 |
| drag over border | 4.53:1 3:1 | 6.50:1 3:1 |
| file name | 18.09:1 AAA | 15.87:1 AAA |
| file size | 4.77:1 AA | 6.32:1 AA |
| error text | 6.57:1 AA | 9.01:1 AAA |
| error icon | 6.57:1 3:1 | 9.01:1 3:1 |
| error border | 4.83:1 3:1 | 4.65:1 3:1 |
| done text | 5.69:1 AA | 10.80:1 AAA |
| done icon | 5.69:1 3:1 | 10.80:1 3:1 |
| progress bar | 3.83:1 3:1 | 8.31:1 3:1 |
| file icon | 4.77:1 3:1 | 9.38:1 3:1 |
| remove button icon | 18.09:1 3:1 | 15.87:1 3:1 |
| remove button icon, hovered | 16.44:1 3:1 | 12.71:1 3:1 |
Text needs 4.5:1, icons and focus rings 3:1. Disabled states are exempt. Full contrast report