File Upload
Drag-and-drop or browse-based file selection with validation, optional image gallery, and preview dialog.
No files selected.
Installation
The component is exported from @by/experience-system. Add the package with your package manager:
pnpm add @by/experience-systemIn this monorepo, depend on the workspace package (for example via workspace:* or your catalog) so imports resolve to packages/experience-system.
Composition
Use FileUpload as a single control for picking files (dropzone + hidden input). Gallery and preview UI are optional branches driven by props and current selection:
FileUpload
├── (hidden file input)
├── dropzone (button)
├── (optional) gallery summary + clear action
├── (optional) gallery grid
├── (optional) validation errors (Alert)
└── (optional) full-screen image preview (Dialog)The root is a div with role="group" wrapping a native input type="file" (hidden) and a button dropzone. When displayGallery is true and there are files, a summary row and thumbnail grid render; image tiles expose preview and remove actions. Validation messages use Alert. Dialog hosts the zoomed image preview.
Usage
import { FileUpload, type FileWithPreview } from '@by/experience-system';FileUpload is a client component ('use client'). Use it inside a Client Component or a dynamic import when using the Next.js App Router.
<FileUpload
title="Upload files"
description="Drag and drop files here or click to browse."
onValueChange={(files) => {
console.log(files.length);
}}
/>Use value and onValueChange for a controlled list of File objects. Use defaultValue for an initial uncontrolled list. Use onFilesChange when you need FileWithPreview (including object URLs for image previews).
When to use
Use FileUpload when you want a consistent BY-styled dropzone, built-in limits (maxFiles, maxSize, accept), and optional thumbnail gallery plus lightbox preview for images.
When not to use
For native forms that must submit multipart/form-data directly from the DOM without React state, a plain input type="file" may be simpler. For highly bespoke layouts, build a custom control on top of native file and drag-and-drop events instead of forcing FileUpload props to fit.
Examples
Overview
Default multi-file dropzone without gallery (same preview as at the top of the page).
No files selected.
Gallery
Image-only accept string, displayGallery, and defaultValue to show thumbnails, totals, clear-all, per-item remove/preview, and the preview dialog.
gallery-1.svg
206 B
gallery-2.svg
206 B
gallery-3.svg
206 B
3 files selected.
API Reference
Subsection titles name the exports from @by/experience-system. FileUpload is a Experience System composite (not a Radix primitive); the prop table documents FileUploadProps.
FileUpload
| Prop | Type | Default |
|---|---|---|
accept | string | — |
buttonLabel | string | 'Select files' |
clearLabel | string | 'Clear all' |
defaultValue | File[] | [] |
description | string | 'Drag and drop files here or click to browse.' |
disabled | boolean | false |
displayGallery | boolean | false |
galleryLabel | string | 'Gallery' |
helperText | string | — |
maxFiles | number | 10 |
maxSize | number | 5 * 1024 * 1024 (5 MB) |
multiple | boolean | true |
name | string | — |
onFilesChange | (files: FileWithPreview[]) => void | — |
onValueChange | (files: File[]) => void | — |
title | string | 'Upload files' |
value | File[] | — |
Also accepts standard div attributes except children, defaultValue, and value, which are reserved for file state (see React.ComponentProps<'div'> omit in source).
| Data attribute | Values |
|---|---|
data-slot | file-upload (root), file-upload-dropzone, file-upload-summary, file-upload-gallery, file-upload-item, file-upload-thumbnail-fallback, file-upload-preview-fallback |
data-dragging | present on root while drag-over is active |
FileUploadProps
Type-only export: the props object for FileUpload. Same members as the FileUpload table above.
FileWithPreview
Object shape used by onFilesChange and internal gallery state.
| Field | Type | Description |
|---|---|---|
id | string | Stable id for list keys and preview bookkeeping |
file | File | Selected file |
preview | string | null | Object URL for image previews, or null for non-images |
Accessibility
- The dropzone is a
buttonsoSpaceandEnteropen the file dialog when focus is on the dropzone and the control is notdisabled. - The root uses
role="group"; selected file count is mirrored in anaria-live="polite"region for screen readers. - The hidden file input uses
aria-hiddenandtabIndex={-1}; keyboard users operate the visible dropzone. - Gallery actions use
aria-labelon preview and remove icon buttons; the preview dialog includes a screen-readerDialogTitle. - Provide meaningful
title,description,buttonLabel, andhelperTextso the purpose and constraints of the control are clear without relying on icons alone.
Keyboard interactions
| Key | Description |
|---|---|
Tab | Moves focus to the dropzone (or away when disabled). |
Space / Enter | Activates the dropzone button and opens the file picker when not disabled. |
Esc | Closes the image preview Dialog when open (Radix dialog behavior). |
Source in the repo: packages/experience-system/src/components/FileUpload/FileUpload.tsx. Agent-oriented contracts: packages/experience-system/src/components/FileUpload/FileUpload.instructions.md.