File Upload

A drag-and-drop zone with a fallback file browser. Validates file count, type (via accept), and optional size limits. Selected files are listed below the zone with individual remove controls.

Preview

Drop files here or click to browse

PDF or images, max 5 MB each

States

Error

Drop files here or click to browse

Accepts .pdf

Please upload a PDF file.

Disabled

Drop files here or click to browse

Implementation

FileUpload.tsx
tsx
import { FileUpload } from "@/components/ui/FileUpload";

// Basic
<FileUpload
  label="Attachment"
  onFilesChange={(files) => console.log(files)}
/>

// Image only, max 5 MB
<FileUpload
  label="Profile photo"
  accept="image/*"
  maxSize={5 * 1024 * 1024}
  onFilesChange={(files) => setPhoto(files[0])}
/>

// Multiple files
<FileUpload
  label="Documents"
  accept=".pdf,.docx"
  multiple
  helperText="PDF or Word, max 10 MB each"
  maxSize={10 * 1024 * 1024}
  onFilesChange={setDocs}
/>

// Error state
<FileUpload
  label="Resume"
  error="Please upload a PDF."
  accept=".pdf"
  onFilesChange={setResume}
/>

Props

PropTypeDefaultDescription
acceptstringMIME types or file extensions (e.g. "image/*" or ".pdf,.docx"). Passed to the native input's accept attribute.
multiplebooleanfalseAllows selecting more than one file.
maxSizenumberMaximum file size in bytes. Files exceeding this are rejected with an inline error.
onFilesChange(files: File[]) => voidCalled whenever the file list changes (add or remove).
labelstringVisible label above the drop zone.
helperTextstringSupplemental hint inside the drop zone. Defaults to showing accept and maxSize constraints.
errorstringExternal error message shown below the drop zone.
disabledbooleanfalsePrevents interaction and dims the control.

ARIA roles

ElementRoleKey attributes
Drop zonebuttontabIndex=0, aria-label='Upload files'
File input(hidden)aria-hidden='true' (activated programmatically)
File listlistaria-label='Selected files'
Remove buttonbutton (implicit)aria-label='Remove {filename}'

Keyboard

KeyAction
Enter / SpaceOpen the native file picker when the drop zone is focused
TabMove focus from the drop zone to individual remove buttons in the file list
Shift+TabMove focus backward through the file list and back to the drop zone
Enter / Space on removeRemove the associated file from the list

Accessibility

  • The drop zone uses role="button" with tabIndex so keyboard users can activate it with Enter or Space — triggering the hidden file input.
  • The actual <input type="file"> is sr-only and aria-hidden — it is activated programmatically. The visible drop zone is the interactive target.
  • Size and type errors appear as visible text; they are not announced via aria-live. If immediate announcement is needed, add role="alert" to the error element.
  • Each file in the list has an individual remove button with an aria-label describing the file being removed ("Remove resume.pdf").
  • The file list uses <ul> with aria-label="Selected files" so screen readers understand the context of the list items.