Skip to content

FileUpload

Anatomy

+-------------------------------------------+
| Label |
| +-------------------------------------+ |
| | | | ← dashed dropzone
| | Click to upload or drag-and-drop | |
| | · .pdf, .docx · up to 10 MB | |
| | | |
| +-------------------------------------+ |
| optional description |
| |
| [ report-q3.pdf 2.4 MB × ] | ← file list
| ▓▓▓▓▓▓▓▓░░░░░░░░░░ 63% |
+-------------------------------------------+

When to use

  • Any place users attach a file: avatar upload, attachment to a message, CSV import, document upload.
  • Pair with <AttachmentTile> if you want a post-upload view with rich previews.

Examples

Single file, size-bounded

<FileUpload
label="Logo"
accept="image/png,image/svg+xml"
maxSize={2 * 1024 * 1024}
onFiles={([file]) => upload(file)}
/>

Multiple files with progress

const [progress, setProgress] = useState<Record<string, number>>({});
<FileUpload
multiple
accept=".pdf,.docx"
onFiles={(files) => files.forEach((f) => uploadWithProgress(f, (pct) => {
setProgress((p) => ({ ...p, [f.name]: pct }));
}))}
progress={progress}
/>

Wired to react-hook-form

const { setValue, watch } = useFormContext<{ files: File[] }>();
<FileUpload
label="Attachments"
multiple
onFiles={(files) => setValue('files', [...(watch('files') ?? []), ...files])}
/>

Tokens

See the tokens block in this spec’s frontmatter.

Install: @helixui/core

import { FileUpload } from '@helixui/core'

status: stable · since: 0.1.0

Tags: file, upload, dropzone, attachment, drag-drop, input

Props

NameTypeDefaultDescription
labelstringundefinedField label rendered above the dropzone.
descriptionReactNodeundefinedHelper text rendered below the dropzone.
hintReactNodeundefinedVisible inside the dropzone — defaults to a “Click to upload or drag and drop” cue.
acceptstringundefinedMIME / extension filter; e.g. “image/*” or “.pdf,.docx”.
maxSizenumberundefinedMax file size in bytes. Files larger are reported via onReject.
multiplebooleanfalseAllow selecting more than one file.
onFiles(files: File[]) => voidundefinedFires when files pass the accept + size filters.
onReject(rejections: { file: File; reason: 'size' | 'type' }[]) => voidundefinedFires for files that fail filtering.
progressRecord<string, number>undefinedMap of file.name → 0-100 percent. helixui renders a progress bar per file.
errorsRecord<string, string>undefinedOptional per-file error messages, keyed by file.name.
tone'brand' | 'neutral'brandTone for the active drop ring.
disabledbooleanfalseDisable interaction.

Tokens used

color.bg.surface.default, color.bg.surface.subtle, color.bg.action.brand.subtle, color.bg.action.brand.default, color.border.subtle, color.border.action.brand, color.text.primary, color.text.secondary, color.text.muted, color.text.action.brand, color.text.danger, radius.sm, radius.md, radius.lg, spacing.1, spacing.2, spacing.3, spacing.5, font.size.xs, font.size.sm, font.weight.medium, font.weight.semibold

Accessibility

Role: button

Keyboard

KeyAction
TabFocus the dropzone.
EnterOpen the native file picker.
SpaceOpen the native file picker.

Notes

  • The dropzone is a labeled button (role="button") for screen readers.
  • Drag-and-drop is a supplementary affordance; keyboard + native picker is the primary path.
  • Each progress bar exposes ARIA progress state with min/max/value.
  • Disabled state removes the dropzone from the tab order.