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
namestring—Required. Carried by the native input.
labelstring—Required. 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.
acceptstring—Native accept syntax: extensions, exact MIME types, or wildcard MIME types.
maxSizenumber—Bytes, per file. No total-size cap.
maxFilesnumber—Multiple 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[]) => void—Fires on every accepted change: add, remove, or single-mode replace.
onreject(rejections: FileRejection[]) => void—Fires whenever a pick or drop yields rejected files — see FileRejection below.
descriptionstring—Help text below the label.
errorstring—Inline error message; sets the error state. The component never sets this itself.
requiredbooleanfalse
disabledbooleanfalse
hideLabelbooleanfalse
elementHTMLInputElement—Bindable. The underlying file input element, for focus and keyboard shortcuts.
classstring—Merged after the hz-field hz-file-upload classes.

Anything not listed above is forwarded as an attribute to the root element (or the native control in form components). So id, data-*, aria-*, and event handlers just work.

FileRejection

NameTypeDefaultNote
fileFile—The rejected file.
reason'type' | 'size' | 'too-many'—Accept mismatch, over maxSize, or beyond the count cap.
messagestring—A 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">