Experience System
Components

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-system

In 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.

Image-only accept string, displayGallery, and defaultValue to show thumbnails, totals, clear-all, per-item remove/preview, and the preview dialog.

Gallery (3/10)

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

PropTypeDefault
acceptstring
buttonLabelstring'Select files'
clearLabelstring'Clear all'
defaultValueFile[][]
descriptionstring'Drag and drop files here or click to browse.'
disabledbooleanfalse
displayGallerybooleanfalse
galleryLabelstring'Gallery'
helperTextstring
maxFilesnumber10
maxSizenumber5 * 1024 * 1024 (5 MB)
multiplebooleantrue
namestring
onFilesChange(files: FileWithPreview[]) => void
onValueChange(files: File[]) => void
titlestring'Upload files'
valueFile[]

Also accepts standard div attributes except children, defaultValue, and value, which are reserved for file state (see React.ComponentProps<'div'> omit in source).

Data attributeValues
data-slotfile-upload (root), file-upload-dropzone, file-upload-summary, file-upload-gallery, file-upload-item, file-upload-thumbnail-fallback, file-upload-preview-fallback
data-draggingpresent 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.

FieldTypeDescription
idstringStable id for list keys and preview bookkeeping
fileFileSelected file
previewstring | nullObject URL for image previews, or null for non-images

Accessibility

  • The dropzone is a button so Space and Enter open the file dialog when focus is on the dropzone and the control is not disabled.
  • The root uses role="group"; selected file count is mirrored in an aria-live="polite" region for screen readers.
  • The hidden file input uses aria-hidden and tabIndex={-1}; keyboard users operate the visible dropzone.
  • Gallery actions use aria-label on preview and remove icon buttons; the preview dialog includes a screen-reader DialogTitle.
  • Provide meaningful title, description, buttonLabel, and helperText so the purpose and constraints of the control are clear without relying on icons alone.

Keyboard interactions

KeyDescription
TabMoves focus to the dropzone (or away when disabled).
Space / EnterActivates the dropzone button and opens the file picker when not disabled.
EscCloses 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.