File Upload
A real <input type="file">, wrapped entirely inside a real <label> that IS the drop zone — the whole padded area is natively clickable, no .click()forwarding needed. Drag-and-drop is a progressive enhancement on top of that: the native element has none, and accept only filters the OS dialog, never a drop, so both are reimplemented once and reused by whichever path a file actually arrives through. The real input stays authoritative either way — dropped files are re-assigned onto its own .files via a fresh DataTransfer, so it still participates correctly in a plain, non-JS <form> submission. While dragging, the active wash scales outward from the pointer's live position; its radius is counter-scaled to preserve the dropzone's roundness, and reduced-motion users get a simple opacity transition. Opt into preview to mould the dropzone around the selection — displayable images fill or tile as object-URL thumbnails; other files show as compact name chips. Pair with aspectRatio="square" for a 1:1 tile that uses Card's concentric radius pair (--kernel-radius-sheet outer, --kernel-radius-md inner via --kernel-padding-sheet).
Playground
Toggle every adjustable prop and watch the component — and the code — update live.
Drag any desktop item into the dropzone — images preview; other types list as files.
Same component — Kernel look stripped. See Platforms.
Usage
<FileUpload label="Upload files" description="Any file type" multiple preview aspectRatio="square" maxFiles={8} errorMessage="That file is too large." /><kernel-file-upload label="Upload files" description="Any file type" multiple preview aspect-ratio="square" max-files="8" error-message="That file is too large."></kernel-file-upload>Props
| Prop | Type | Default |
|---|---|---|
label | ReactNode (required) | — |
hideLabel | boolean | false |
description | ReactNode | — |
files | File[] | — |
defaultFiles | File[] | [] |
accept | string | — |
multiple | boolean | false |
maxFiles | number | — |
maxSize | number (bytes) | — |
preview | boolean | false |
aspectRatio | "auto" | "square" | "auto" |
onError | (error) => void | — |
errorMessage | ReactNode | — |
invalid | boolean | false |
disabled | boolean | false |
Rejections (wrong type, over maxSize, over maxFiles, or a dropped folder rather than a file) reject the whole attempted batch and call onError rather than silently keeping only some of what was dropped. Dropping accumulates onto the existing selection; re-opening the OS picker still replaces it, the same as the native element's own behaviour — that inconsistency comes from the browser, not from this wrapper.
Accessibility
- Drag-and-drop is a progressive enhancement only: Tab to the drop zone, Enter or Space opens the native OS dialog, fully usable with zero pointer/drag involvement.
- Each file's remove button gets
aria-label="Remove {filename}"since it's otherwise an icon-only control. - Rejected files go through the same
role="alert"error region as every other Kernel form field'serrorMessage.