Skip to content

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 ·

File upload

Installation

bunx --bun shadcn@latest add @dga/file-upload

Installs Field too.

Usage

page.tsx
tsx
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.

Validation

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.

States

Reporting progress

Control the list with files and onFilesChange, and update the file while it uploads.

page.tsx
tsx
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.

LTR (English)
RTL (Arabic)

Accessibility

KeyAction
TabMoves focus to the drop zone, then through the remove buttons.
Space or EnterOpens the file picker.
Enter or Space on a remove buttonRemoves 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" with aria-valuenow; the percentage is also written next to it.
  • Translate messages on 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.

FileUpload props
PropTypeDefault
string

Name for form submission.

No default
UploadFile[]

The files in the list (controlled). Update `status` and `progress` as uploads run.

No default
string

Accepted formats, as in the HTML `accept` attribute: `.pdf,image/*`.

No default
number

Largest size per file, in bytes.

No default
number

Most 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[]) => void

Called with the whole list whenever a file is added or removed.

No default
(files: UploadFile[]) => void

Called with the files that passed validation, so you can start uploading them.

No default
boolean

Allows 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
boolean

Disables the drop zone.

No default
boolean

The 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.
ChangeReason
File Upload: drop zoneA 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 namesA name mixing Arabic and Latin text must not be reordered around its extension.

View all 2 deviations for this component

Contrast

70 pairs checked · all pass WCAG 2.1 AAShow 23 states
Variant / stateLightDark
drop zone border4.57:1 3:13.80:1 3:1
drop zone border, hovered9.73:1 3:112.04:1 3:1
drop zone border, drag over4.54:1 3:17.73:1 3:1
drop zone border, error4.62:1 3:14.65:1 3:1
focus ring17.31:1 3:117.49:1 3:1
hint4.57:1 AA6.32:1 AA
drop zone text18.09:1 AAA15.87:1 AAA
drop zone text, hovered17.31:1 AAA17.04:1 AAA
drop zone text, drag over17.29:1 AAA13.36:1 AAA
drop zone icon4.74:1 3:110.38:1 3:1
drop zone icon, drag over4.53:1 3:18.74:1 3:1
drag over border4.53:1 3:16.50:1 3:1
file name18.09:1 AAA15.87:1 AAA
file size4.77:1 AA6.32:1 AA
error text6.57:1 AA9.01:1 AAA
error icon6.57:1 3:19.01:1 3:1
error border4.83:1 3:14.65:1 3:1
done text5.69:1 AA10.80:1 AAA
done icon5.69:1 3:110.80:1 3:1
progress bar3.83:1 3:18.31:1 3:1
file icon4.77:1 3:19.38:1 3:1
remove button icon18.09:1 3:115.87:1 3:1
remove button icon, hovered16.44:1 3:112.71:1 3:1

Text needs 4.5:1, icons and focus rings 3:1. Disabled states are exempt. Full contrast report