Skip to content

FileUpload

A file selection field backed by a real native input, single or multiple, with accept, maxSize, and maxFiles validation, a removable file list, and an optional drag-and-drop dropzone. It selects files rather than uploading them: the named input carries the chosen files into a plain form submission or into your own upload code.

Import

import { FileUpload } from "@hyzer-labs/ui"

Demo

Single-file mode: an accepted pick replaces the current selection.

let file = $state<File[]>([]);

<FileUpload name="resume" label="Resume" bind:files={file} />

Props

NameTypeDefaultNote
namestringRequired. Carried by the native input.
labelstringRequired. Always in the DOM; sr-only with hideLabel.
filesFile[][]Bindable. Selected files in selection order — not a FileList.
multiplebooleanfalseSingle mode replaces on each pick; multiple mode appends + de-dupes.
acceptstringNative accept syntax: extensions, exact MIME types, or wildcard MIME types.
maxSizenumberBytes, per file. No total-size cap.
maxFilesnumberMultiple mode only — ignored in single mode (the cap is always 1).
dropzonebooleanfalseSwaps the visible input for a drag-and-drop surface + activation button.
buttonTextstring'Browse files'Dropzone mode only.
dropzoneTextstring'Drag and drop files here, or'Dropzone mode only.
onchange(files: File[]) => voidFires on every accepted change: add, remove, or single-mode replace.
onreject(rejections: FileRejection[]) => voidFires whenever a pick or drop yields rejected files — see FileRejection below.
descriptionstringHelp text below the label.
errorstringInline error message; sets the error state. The component never sets this itself.
requiredbooleanfalse
disabledbooleanfalse
hideLabelbooleanfalse
classstringMerged after the hz-field hz-file-upload classes.

FileRejection

NameTypeDefaultNote
fileFileThe rejected file.
reason'type' | 'size' | 'too-many'Accept mismatch, over maxSize, or beyond the count cap.
messagestringA ready-to-display English string.

Theme hooks

What this component promises your CSS. The reference theme styles exactly these — from @layer hz-theme, so your unlayered rules win. See Styling Components for the how.

Root class: .hz-field hz-file-upload

Data attributes

HookValuesStyles
data-state'default' | 'error' | 'disabled'The universal field state hook, on the root. Precedence is fixed: error beats disabled.
data-dropzonepresent in dropzone modeOn the root. The reference theme discriminates the two modes by targeting the parts instead, so this one is yours.
data-dragoverpresent while dragging overOn .hz-file-dropzone. Backed by a depth counter, so it survives dragging across child elements.

Part classes

HookValuesStyles
.hz-field-labelchild elementThe label.
.hz-field-requiredchild elementThe required marker.
.hz-field-descriptionchild elementThe hint text.
.hz-field-errorchild elementThe error message.
.hz-file-controlchild elementThe box in basic mode.
.hz-file-dropzonechild elementThe box in dropzone mode.
.hz-file-dropzone-textchild elementThe dropzone prompt.
.hz-file-buttonchild elementThe browse button.
.hz-file-listchild elementThe selected-file list.
.hz-file-itemchild elementOne file row.
.hz-file-namechild elementThe file name.
.hz-file-sizechild elementThe formatted size.
.hz-file-removechild elementThe per-file remove button. Also load-bearing: focus placement after a removal queries it.

Accessibility

Drag-and-drop is a progressive enhancement, so the non-drag alternative is always present. In basic mode, the visible native input opens the platform picker directly. In dropzone mode, a real <button> (buttonText) opens the same picker. No function ever depends on a dragging movement (WCAG 2.5.7).

The native input is labeled by the field's <label for>. In dropzone mode the input is aria-hidden and out of the tab order, because the button is the control you operate. description and error chain into aria-describedby on the input, description first. required sets aria-required, and an error sets aria-invalid.

Each selected file's remove button carries its own Remove label naming that file. Additions, removals, and rejection counts are announced through a polite aria-live status region. That region sits outside the file list, so screen reader users hear changes the re-rendered list alone would not surface.

The theme's dragover state changes the background and the border, not the color alone.

References: MDN: <input type="file">