File Upload
import { FileUpload } from '@absoluteui/components/file-upload';import '@absoluteui/components/file-upload/style.css';Using Angular? See File Upload for Angular.
What it does
Section titled “What it does”A file picker for forms. It provides:
- a native
<input type="file">opened by a real<button>(keyboard and screen-reader path); - an optional drag-and-drop zone around the button (an enhancement, never the only way in);
- validation of MIME type and extension (both, when both are configured), per-file size, total size, and file count. The
acceptattribute is only a hint: every file is re-checked; - rejected files are not added; the reason is shown next to the field and announced;
- a list of selected files with remove buttons;
- the
FormControlcontract:Formvalidates, serializes (File[]), resets, and focuses it.
It never reads file contents and never performs network requests. To transfer files with progress, feed an Upload Queue.
When to use
Section titled “When to use”- Attaching one or more files to a form.
- Feeding an Upload Queue that transfers files as soon as they are chosen.
- Any file selection that needs consistent, localized validation messages.
When not to use
Section titled “When not to use”- Choosing a folder tree or very large batches (thousands of files): use a dedicated tool.
- Capturing photos or media in-page: use native capture APIs.
- As a security control: client-side type checks can be bypassed; validate on the server.
Examples
Section titled “Examples”Multiple attachments with limits
Section titled “Multiple attachments with limits”import { FileUpload } from '@absoluteui/components/file-upload';
const host = document.querySelector<HTMLElement>('#attachments');if (host) { const upload = FileUpload.create(host, { label: 'Attachments', name: 'attachments', multiple: true, acceptTypes: ['application/pdf', 'image/*'], acceptExtensions: ['.pdf', '.png', '.jpg'], maxFileSize: 5_000_000, maxFiles: 3, }); upload.host.addEventListener('aui-change', () => { const data = new FormData(); for (const file of upload.getFormValue()) data.append('attachments', file); });}Single image without a drop zone
Section titled “Single image without a drop zone”import { FileUpload } from '@absoluteui/components/file-upload';
const host = document.querySelector<HTMLElement>('#avatar');if (host) { FileUpload.create(host, { label: 'Profile photo', required: true, acceptTypes: ['image/png', 'image/jpeg'], maxFileSize: 2_000_000, dropZone: false });}import { FileUpload, type FileUploadRejectDetail } from '@absoluteui/components/file-upload';import '@absoluteui/components/file-upload/style.css';
const host = document.querySelector<HTMLElement>('#photos');if (host) { const upload = FileUpload.create(host, { label: 'Photos', multiple: true, acceptTypes: ['image/*'], maxTotalSize: 20_000_000 }); upload.host.addEventListener('aui-reject', (e) => { console.info(`${String((e as CustomEvent<FileUploadRejectDetail>).detail.rejected.length)} files rejected`); });}More usage
Section titled “More usage”import { FileUpload } from '@absoluteui/components/file-upload';
const host = document.createElement('div');document.body.append(host);FileUpload.create(host, { label: 'Attachments', name: 'attachments', multiple: true, acceptExtensions: ['.pdf'], maxFileSize: 5_000_000 });Anatomy
Section titled “Anatomy”<div class="aui-field aui-file-upload [--dragover] [--invalid] [--disabled] [--no-zone]"> <label class="aui-field__label" for="B" id="L">Attachments<span aria-hidden="true">*</span></label> <div class="aui-field__description" id="D">help text</div> <div class="aui-file-upload__zone"> (drop target) <span class="aui-file-upload__zone-icon"><svg aria-hidden="true"/></span> <span class="aui-file-upload__prompt">Drag files here or</span> <button type="button" id="B" class="aui-file-upload__button" aria-labelledby="L T" aria-describedby="D H R E X"> <span id="T">Choose files</span> </button> <div class="aui-file-upload__hint" id="H">Allowed file types: .pdf. Maximum 5 MB per file.</div> <span class="aui-visually-hidden" id="R">Required</span> <input type="file" hidden tabindex="-1" name="attachments" multiple accept=".pdf"> </div> <div class="aui-field__error" id="E">...</div> <div class="aui-file-upload__rejections" id="X"><ul><li>report.exe: This file type is not allowed.</li></ul></div> <ul class="aui-file-upload__list" aria-label="Selected files"> <li class="aui-file-upload__item [--invalid]"> <svg aria-hidden="true"/> <span class="aui-file-upload__file"><span class="__name">a.pdf</span><span class="__size">1.2 MB</span><span class="__issue"></span></span> <button class="aui-file-upload__remove" aria-label="Remove a.pdf"><svg aria-hidden="true"/></button> </li> </ul></div>API reference
Section titled “API reference”Factory
Section titled “Factory”FileUpload.create(host: HTMLElement, options: FileUploadOptions): FileUploadGlobal configuration key: fileUpload (see Configuration).
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
label |
string |
Visible label and accessible name (part of the button name). Required. | |
hideLabel |
boolean |
false |
Visually hide the label (it stays in the accessible name). |
description |
string |
Help text, referenced by aria-describedby. | |
name |
string |
Form field name; also set on the native input for native submission. | |
multiple |
boolean |
false |
Allow several files; a new selection appends. Single mode replaces the file. |
value |
readonly File[] |
"[]" |
Initial files; reset() restores them. |
required |
boolean |
false |
At least one file is required. |
disabled |
boolean |
false |
Disable the button, drop zone, and remove buttons. |
acceptTypes |
readonly string[] |
Accepted MIME types (“image/png”, “image/*”). Every file is checked. | |
acceptExtensions |
readonly string[] |
Accepted extensions including the dot (“.pdf”). When both lists are set, a file must match both. | |
maxFileSize |
number |
Maximum size of each file, in bytes. | |
maxTotalSize |
number |
Maximum combined size, in bytes. | |
maxFiles |
number |
Maximum number of files (requires multiple). | |
buttonText |
string |
Button text. Default: localized “Choose file” / “Choose files”. | |
validate |
(files: readonly File[]) => string | null |
Consumer validation; return a message to mark the value invalid. | |
icons |
Partial<{ upload: IconRef; file: IconRef; remove: IconRef; error: IconRef }> |
Icon slot overrides. | |
validationTiming |
'blur' | 'input' | 'submit' |
"blur" |
When validation messages first appear; afterwards they update live. Globally configurable. |
dropZone |
boolean |
true |
Render the drag-and-drop zone around the button. Globally configurable. |
showConstraints |
boolean |
true |
Render a hint listing the type, size, and count limits. Globally configurable. |
locale |
string |
"en-US" |
Message and size-formatting locale. Globally configurable. Inherited from common configuration. |
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
host |
HTMLElement |
Host element. |
input |
HTMLInputElement |
The native file input (hidden; the button opens it). |
button |
HTMLButtonElement |
The chooser button. |
value |
readonly File[] |
Selected files in selection order. |
validity |
FileUploadValidity |
Current validity. |
rejections |
readonly FileRejection[] |
Files not added by the last selection, with reasons. |
name |
string | null |
Form field name. |
label |
string |
Accessible label (used by the error summary). |
disabled |
boolean |
Whether the control is disabled. |
dirty |
boolean |
Value differs from the initial value. |
touched |
boolean |
Focus has left the control at least once. |
options |
Readonly<FileUploadOptions> |
Current options. |
Methods
Section titled “Methods”| Method | Description |
|---|---|
getFormValue(): File[] |
A copy of the selected files. |
setValue(files: readonly File[]): void |
Replace the selection (no events, no filtering; violations make the value invalid). |
removeFile(file: File): void |
Remove one file (no events). |
openPicker(): void |
Open the native chooser (needs user activation). |
update(options: Partial<FileUploadOptions>): void |
Merge and apply options. |
reportValidity(): boolean |
Show messages now (marks the control submitted) and return validity. |
setCustomError(message: string | null): void |
External error (server, cross-field rule); null clears it. |
focus(): void |
Move focus to the primary focusable element. |
reset(): void |
Restore the initial value and clear dirty/touched/submitted state. |
destroy(): void |
Remove rendering, registration, listeners, and the configuration subscription. |
Events
Section titled “Events”| Event | Detail | Bubbles | Cancelable | Description |
|---|---|---|---|---|
aui-change |
{ files: readonly File[]; added: readonly File[]; removed: readonly File[]; validity: FileUploadValidity } |
Yes | No | The user added (chooser or drop) or removed files. |
aui-reject |
{ rejected: readonly { file: File; reason: FileRejectionReason; message: string }[] } |
Yes | No | A selection contained files that were not added. |
CSS classes
Section titled “CSS classes”All rules live in the aui.components cascade layer, so application styles override them without specificity battles.
| Class | Purpose |
|---|---|
aui-file-upload |
Host (also aui-field). |
aui-file-upload--dragover | --invalid | --disabled | --no-zone |
State modifiers. |
aui-file-upload__zone |
Drop zone around the button. |
aui-file-upload__zone-icon |
Upload icon. |
aui-file-upload__prompt |
“Drag files here or” text. |
aui-file-upload__button |
Chooser button. |
aui-file-upload__hint |
Constraint hint. |
aui-file-upload__rejections |
Rejected files and reasons. |
aui-file-upload__list |
Selected files. |
aui-file-upload__item |
One file row (–invalid when it breaks a rule). |
aui-file-upload__name | __size | __issue |
Row text parts. |
aui-file-upload__remove |
Remove button. |
Icon slots
Section titled “Icon slots”| Slot | Default icon |
|---|---|
upload |
upload |
file |
file |
remove |
close |
error |
alert-circle |
Design tokens
Section titled “Design tokens”Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.
--aui-border-width | --aui-border-width-strong | --aui-color-border | --aui-color-border-strong | --aui-color-disabled-bg | --aui-color-error-fg | --aui-color-focus-ring | --aui-color-hover | --aui-color-selected | --aui-color-selected-text | --aui-color-surface | --aui-color-text | --aui-color-text-disabled | --aui-color-text-muted | --aui-control-height-md | --aui-control-padding-x | --aui-duration-fast | --aui-easing-standard | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-font-size-md | --aui-font-size-sm | --aui-font-weight-medium | --aui-icon-size-lg | --aui-icon-size-md | --aui-icon-size-sm | --aui-line-height-tight | --aui-radius-md | --aui-radius-sm | --aui-space-1 | --aui-space-2 | --aui-space-3 | --aui-space-4 | --aui-touch-target-min
TypeScript definitions
Section titled “TypeScript definitions”type FileUploadErrorCode = 'value-missing' | 'file-too-large' | 'file-type' | 'too-many-files' | 'total-too-large' | 'custom';type FileRejectionReason = 'file-too-large' | 'file-type' | 'too-many-files' | 'total-too-large' | 'folder';interface FileUploadValidity { readonly valid: boolean; readonly error: FileUploadErrorCode | null; readonly message: string }interface FileRejection { readonly file: File; readonly reason: FileRejectionReason; readonly message: string }interface FileUploadConfig { validationTiming: ValidationTiming; dropZone: boolean; showConstraints: boolean }interface FileUploadOptions extends Partial<FileUploadConfig> { label: string; hideLabel?: boolean; description?: string; name?: string; multiple?: boolean; value?: readonly File[]; required?: boolean; disabled?: boolean; acceptTypes?: readonly string[]; acceptExtensions?: readonly string[]; maxFileSize?: number; maxTotalSize?: number; maxFiles?: number; buttonText?: string; validate?: (files: readonly File[]) => string | null; locale?: string; icons?: Partial<FileUploadIcons>;}interface FileUploadChangeDetail { files: readonly File[]; added: readonly File[]; removed: readonly File[]; validity: FileUploadValidity }interface FileUploadRejectDetail { rejected: readonly FileRejection[] }Accessibility
Section titled “Accessibility”Pattern: Native <input type=“file”> opened by a native <button>; the drop zone is an enhancement (WCAG 2.5.7: dragging is never required).
Keyboard
Section titled “Keyboard”| Key | Context | Action |
|---|---|---|
| Tab / Shift+Tab | Any | Moves between the chooser button and each remove button. |
| Enter / Space | Chooser focused | Opens the native file chooser. |
| Enter / Space | Remove button focused | Removes the file; focus moves to the next remove button, else the previous one, else the chooser. |
| Element | Role |
|---|---|
| chooser | button (native) |
| native input | hidden; not in the tab order |
| selected files | list (native <ul>, aria-label “Selected files”) |
| remove buttons | button (native), named “Remove {file}” |
| icons | none (aria-hidden=true) |
ARIA attributes
Section titled “ARIA attributes”| Attribute | Element | Value |
|---|---|---|
aria-labelledby |
chooser | Label id and button-text id (“Attachments Choose files”) |
aria-describedby |
chooser | Help text, constraint hint, “Required”, visible error, and rejection ids |
aria-label |
remove button | Remove {file name} |
The chooser button and remove buttons show a :focus-visible outline in color-focus-ring. After a drop, focus moves to the chooser button so the result is reachable.
WCAG 2.2 success criteria addressed
Section titled “WCAG 2.2 success criteria addressed”Automated test results: Accessibility and compliance.
Notes and known caveats
Section titled “Notes and known caveats”- A button cannot carry aria-required, so “Required” is a visually hidden description.
- Rejections are announced politely: “{file} was not added. {reason}” or “{count} files were not added.”
- Removal is announced: “{file} removed”.
- Errors are text with an icon, never color alone.
- Manual screen-reader verification (NVDA, JAWS, VoiceOver) is pending.
- Native drag and drop has no keyboard equivalent by design; the chooser button is the keyboard and screen-reader path.
Behavior
Section titled “Behavior”States
Section titled “States”empty, files selected, dragging over, files rejected, required, invalid, disabled.
State transitions
Section titled “State transitions”| From | Trigger | To |
|---|---|---|
| empty / selected | chooser or drop with valid files | selected (appended in multiple mode, replaced in single mode); aui-change |
| any | selection with rule-breaking files | those files are not added; rejections shown and announced; aui-reject |
| selected | remove button | file removed, focus to next remove button / previous / chooser; announced; aui-change |
| idle | files dragged over the zone | --dragover until they leave or drop |
| any | update() with stricter rules |
existing files re-validated; value may become invalid (per-row reasons shown) |
Accessibility notes
Section titled “Accessibility notes”- Dragging is never required (WCAG 2.5.7): the button is the primary path; after a drop, focus moves to it.
- Rejections are announced politely (“report.exe was not added. This file type is not allowed.” or “3 files were not added.”), removals too (“a.pdf removed”).
- Errors use text and an icon, never color alone. Remove buttons have a 24 px target (2.5.8).
- Manual screen-reader verification is pending.
Responsive behavior
Section titled “Responsive behavior”Full width of its container. The drop zone content wraps and centers; file rows wrap size and reason under the name; it reflows at 320 CSS px.
Right-to-left
Section titled “Right-to-left”Logical properties throughout; icons and remove buttons follow the inline direction. File names render as text in their own direction.
Theming
Section titled “Theming”Dashed color-border-strong zone on color-surface; drag-over switches to color-selected with color-selected-text; errors use color-error-fg. Forced-colors mode draws CanvasText borders and a Highlight drag-over border.
Global configuration
Section titled “Global configuration”configureAbsoluteUI({ components: { fileUpload: { dropZone: false, showConstraints: true, validationTiming: 'submit' } } });Changes apply to existing instances immediately.
Component configuration
Section titled “Component configuration”Instance options override global and theme defaults. All options are dynamic.
icons.upload (default upload), icons.file (file), icons.remove (close), icons.error (alert-circle).
Security
Section titled “Security”- File names, reasons, and messages are rendered with
textContent(never HTML). - The browser’s MIME type is usually derived from the file name and is not proof of content. Configure both
acceptTypesandacceptExtensionsfor defense in depth, and always validate type (for example by magic bytes) and size on the server. - The
acceptattribute only filters the OS dialog; drops and “All files” choices are re-checked. - No file contents are read. CSP-safe (no inline styles or HTML sinks).
Data handling
Section titled “Data handling”Files stay in memory as File objects and in the native input. Nothing is persisted, logged, or transmitted.
Serialization:
getFormValue()andForm.getValues()returnFile[]under the field name (always an array, also in single mode;[]when empty).Fileobjects are not JSON-serializable: append them to aFormDatato send them:
const data = new FormData();for (const file of values.attachments as File[]) data.append('attachments', file);- The native input is kept in sync (through
DataTransfer), sonew FormData(formElement)and native form submission (submitMode: 'native',enctype="multipart/form-data") include exactly the selected files, including dropped ones.
Performance
Section titled “Performance”File rows are rebuilt only when the selection changes; validation passes update text only. Budgets: FileUpload.create < 1 ms per instance; selecting 200 files (validate and render) < 100 ms (headless Chromium, e2e performance suite).
Supported data scale
Section titled “Supported data scale”Tested with 200 files in one selection. For thousands of files, use a dedicated tool.
Edge cases
Section titled “Edge cases”- The same file (name, size, modification time) selected twice is ignored.
- Single mode: a new file replaces the current one; if it is rejected, the current file is kept. Extra dropped files are rejected (“You can select only 1 file.”).
- Count and total limits reject files in selection order; later smaller files can still fit.
- Dropped folders are rejected (“Folders cannot be added.”).
- A cancelled OS dialog changes nothing.
- Sizes use decimal units:
maxFileSize: 5_000_000reads “5 MB”.
Errors
Section titled “Errors”| Code | Category | Cause |
|---|---|---|
FILE_UPLOAD_OPTIONS |
configuration | Missing label, malformed acceptTypes/acceptExtensions, non-positive sizes, maxFiles without multiple, non-File value. |
CFG_INVALID_VALUE / CFG_UNKNOWN_OPTION |
configuration | Invalid or unknown configuration. |
Validity error codes: value-missing, file-type, file-too-large, too-many-files, total-too-large, custom. A failed construction leaves the host untouched.
Known limitations
Section titled “Known limitations”- Decimal size units only.
- The drag-over highlight depends on the browser exposing
Filesduringdragenter. - Browsers without a
DataTransferconstructor cannot sync the native input;getFormValue()stays authoritative. - Manual screen-reader verification is pending.
Unsupported scenarios
Section titled “Unsupported scenarios”- Reading, previewing, or transforming file contents (Upload Queue offers image previews).
- Folder upload (
webkitdirectory), paste-to-upload. - HTML in labels, hints, or file names.