Skip to main content

Design system components

FileUpload

File picker with size validation and clear control.

Status: stable. Group: form. Markdown version: /design-system/components/file-upload.md

When to use

FileUpload is the single-file picker: a readonly summary field showing "name (size)" plus Browse and Clear buttons driving a hidden native input. It enforces maxSizeInBytes client-side (rejects, clears, and shows a localized error) and filters the picker via accept. Two appearances: default (TextInput + Button) and editorial (Combobox-style field for editorial forms).

When not to use

  • Do not render two FileUploads in the same form without unique labels. The labels disambiguate the AT context; identical labels collide.
  • Do not rely on value for "the file is already on the server". FileUpload is for *selection*, not display of an already-uploaded asset. Render an uploaded asset as Link + Clear control instead.
  • Do not skip the Clear button. Users who picked the wrong file have no recovery path without it on mobile (the platform sheet doesn't expose a "clear" affordance once a file is bound).
  • Do not use value to represent an already-uploaded server file; it only mirrors a local File object.
  • Do not use it for multi-file needs; it holds exactly one file (last pick wins).

Look-alikes

The contract names no look-alike with a deciding condition.

Props

FileUpload props
PropTypeDefaultDescriptionRequired
labelstringnoneField label.Yes
placeholderstringnonePlaceholder shown when no file is selected.No
helperTextstringnoneHelper copy below the control.No
uploadButtonLabelstringnoneUpload button label.Yes
clearButtonLabelstringnoneClear/remove button label; the button shows only while a file is set, and activating it moves focus to the upload button.No
acceptstringnoneAccepted file extensions/MIME types for the native picker.No
maxSizeInBytesnumbernoneMaximum file size in bytes; checked when a file is picked.No
sizeErrorMessagestringnoneError shown when a picked file exceeds maxSizeInBytes.No
errorstringnoneExternal error message; renders above the helper line.No
valueFile | nullnullControlled File value.No
onFileChange(file: File | null) => voidnoneCalled when a file is selected or cleared.No
disabledbooleanfalseDisables the control.No
requiredbooleanfalseMarks the field required.No
appearance"default" | "editorial"defaultMatch FormFieldEditorial / Combobox styling on editorial forms.No
classNamestringnoneNo description in the contract.No

Variants

  • Allowed appearance values: default, editorial (default default).

Invalid combinations

The contract records no invalid combinations.

Keyboard

FileUpload keyboard behaviour
KeyBehaviour
TabMoves focus to the control and on to the next focusable element. (standard)
EnterActivates the focused control. (standard)
SpaceActivates or toggles the focused control. (standard)

Rows marked (standard) name a key the contract lists without a description; the behaviour shown is the usual one for that key.

Rules

Every item under When not to use is a MUST NOT. MUST and SHOULD follow RFC 2119.

FileUpload rules
LevelRuleHow it is checked
MUSTARIA: Label association.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Error via an error HelperText (role=alert), announced once when it appears. Both appearances reference the error and then the helper text from the control with aria-describedby and set aria-invalid while an error shows.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Clear moves focus to the upload button before it unmounts, so focus never drops to the page body.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: With required, the editorial label adds an asterisk (aria-hidden) and a translated sr-only hint (fieldRequiredHint, English fallback "required")Not yet verified: no evidence record. WCAG 4.1.2.
MUSTRequired stories produce no axe violations at the configured severity.Automated by npm run test:stories:matrix:ci. WCAG 2.5.8.
MUSTThe accessibility tree (role, name, state) matches the committed snapshot for every required story.Automated by npm run test:stories:matrix:ci. WCAG 1.3.1, 4.1.2.
MUSTThe component remains legible and operable under a real browser forced-colors: active pass.Automated by npm run test:stories:hc.
MUSTEvery documented keyboard interaction works as specified.Not yet verified: no evidence record. WCAG 2.1.1, 2.1.2.
MUSTA human completed an end-to-end accessibility review of this component.Manual review, last on 2026-11-04.
SHOULDAlways set accept and maxSizeInBytes; a picker without limits fails at the server instead of at the field.Not stated in the contract.
SHOULDPass clearButtonLabel so users can undo a wrong pick without re-opening the OS dialog.Not stated in the contract.
SHOULDState the accepted formats and size cap in helperText; the accept filter is invisible until the dialog opens.Not stated in the contract.
SHOULDTreat onFileChange(null) as "cleared": reset your form state and any pending upload.Not stated in the contract.
SHOULD NOTDo not rely on the size check as security; it is UX-only and must be re-validated server-side.Not stated in the contract.

Install and import

  • Install: npm install @digitaltableteur/react (version 0.9.1, restricted access on npm).
  • Import: import { FileUpload } from "@digitaltableteur/react";
  • Entry point: @digitaltableteur/react/forms exports it too.
  • Styles: import @digitaltableteur/react/style.css once at the app boundary.
  • Also available as the custom element <dt-file-upload>; see the custom elements manifest.