File Uploadv1.1.4Documentation Under Review
A flexible file upload component with drag-and-drop support, file validation, progress tracking, and customizable trigger coloration built for product applications.
Prefer AI-assisted development?
Catalyst File Upload wraps react-dropzone with a compound-component API and built-in Catalyst design tokens — giving you drag-and-drop, validation, progress tracking, and error recovery with zero boilerplate.
Installation
npm install @pmi/catalyst-file-uploadimport {
FileUploadDropzone,
FileUploadDropzoneArea,
FileUploadTrigger,
FileUploadDescription,
FileUploadMessage,
FileUploadFileList,
FileUploadFileListItem,
FileUploadFileMessage,
FileUploadRemoveFile,
FileUploadRetryFile,
useFileUpload,
useFileUploadFileListContext,
} from '@pmi/catalyst-file-upload';Add the preset to your tailwind.config.ts:
import { tailwindFileUploadPreset } from '@pmi/catalyst-file-upload';
export default {
presets: [tailwindFileUploadPreset],
// ...
};Variants
Background
Use coloration="background" on FileUploadTrigger to apply a subtle filled background to the dropzone area.
Neutral
Use coloration="neutral" for a white (light) / dark-surface (dark) dropzone background.
Danger
Set isInvalid on FileUploadDropzone to communicate a validation error — the trigger and message adopt red tones, and FileUploadDescription is hidden so FileUploadMessage takes its place.
Disabled
Set disabled on FileUploadDropzone to prevent file selection and apply reduced-opacity styles.
Common Patterns
Card Wrapper
Wrap the upload component in a Card for elevated visual hierarchy on content-rich pages.
Preview Image
Display uploaded images with aspect-ratio preview thumbnails and per-item progress bars.
Preview Image Card
Combine image previews with a Card wrapper for an elevated, content-rich upload experience.
Uploaded images render as aspect-ratio thumbnails inside a bordered card container.
Slow Upload Announcements
For long-running uploads, keep screen reader users informed with a single role="status" / aria-atomic="true" live region that is always rendered.
Completion is always announced ("File uploaded successfully: photo.jpg."); files that finish together are combined into one message ("2 files uploaded successfully: a.jpg, b.png.").
Once an upload takes longer than 3 seconds, the region also announces when it starts ("Uploading photo.jpg…") and its progress every 10 seconds. Focus never moves.
API Reference
File Upload is a compound component built on react-dropzone. The sections below summarize the API surface for each part.
FileUploadDropzone
The root context provider. Spread the return value of useFileUpload onto it and add isInvalid / disabled as needed.
Accepts all props returned by useFileUpload plus children. Forwards that state to all descendant components via context.
FileUploadTrigger
The dashed-border dropzone area. Renders a <label> wrapping a hidden <input type="file">.
Prop
Type
FileUploadDescription
Descriptive helper text displayed below the trigger (accepted types, size limits, etc.).
Accepts all standard <p> HTML attributes. The unstyled prop removes default CVA classes for full className override.
FileUploadMessage
Root-level validation / error message. Reads the dropzone's rootError from context and auto-renders when present.
Prop
Type
FileUploadFileList
Container (<ul>) for the list of uploaded file items. It has no accessible name by default; pass aria-labelledby pointing to a visible heading (e.g. "Uploaded Files").
Accepts all standard <ul> HTML attributes. The unstyled prop removes default CVA classes.
FileUploadFileListItem
Individual file row (<li>). Automatically derives isInvalid from file.status === 'error' and, in that state, sets aria-describedby to the item's FileUploadFileMessage.
Prop
Type
FileStatus
Prop
Type
FileUploadFileMessage
Per-item message inside FileUploadFileListItem. Shows the file's error automatically when its status is 'error'.
Accepts all standard <p> HTML attributes.
FileUploadRemoveFile
Wrapper for the remove button. Use asChild to render your own Button component inside it. Removes the file on click and moves focus (see Focus management); don't add your own onClick={onRemoveFile}.
Accepts all standard <button> HTML attributes plus asChild and unstyled.
FileUploadRetryFile
Wrapper for the retry button on failed uploads. Use asChild to render your own Button component inside it. Calls onRetry on click and sets aria-disabled when the file can't be retried.
Its default accessible name is the generic "retry", so pass a descriptive aria-label such as `Retry upload ${file.fileName}`.
Accepts all standard <button> HTML attributes plus asChild and unstyled.
useFileUpload
Hook that wires react-dropzone with Catalyst's file-status reducer. Pass its return value to FileUploadDropzone.
const dropzone = useFileUpload({
onDropFile: async (file: File) => {
return { status: 'success', result: uploadedUrl };
// or on failure:
return { status: 'error', error: 'Upload failed.' };
},
validation: {
accept: { 'image/*': ['.png', '.jpg', '.jpeg'] },
maxSize: 10 * 1024 * 1024,
maxFiles: 10,
},
});Returns fileStatuses, isInvalid, disabled, getInputProps, and other dropzone state.
useFileUploadFileListContext
Access per-item context inside a FileUploadFileListItem for remove / retry callbacks.
Returns onRemoveFile, onRetry, canRetry, fileStatus, disabled, messageId, and dropzoneId (the file input ID).
Accessibility
File Upload is designed to meet WCAG 2.2 AA standards.
Keyboard Interactions
| Key | Action |
|---|---|
Tab / Shift+Tab | Moves focus to the file input, then through remove / retry buttons in the file list |
Space or Enter | On the file input: opens the native file picker |
Space or Enter | On a remove / retry button: activates it |
Drag-and-drop is an optional shortcut; every file can also be selected with the keyboard through the native file picker.
Labels and descriptions
- Pass
aria-labelledby(e.g. the ID of the visible heading) toFileUploadTriggerto give the file input an accessible name. Anyaria-describedbyyou pass (e.g. the ID of the "Drag files here…" instructions) is also forwarded to the input. - The file input is automatically described by
FileUploadDescription. On a root-level error (a validation error orisInvalidset without failed uploads) the description is removed and the input getsaria-invalid="true"plus theFileUploadMessageID inaria-describedby. Failed uploads add their ownFileUploadFileMessageIDs. They keep the description only when there is no root-level error; when a root error is present, the description is hidden andFileUploadTriggerreferences theFileUploadMessageID and the failed files' message IDs. - Each failed
FileUploadFileListItemis described by its ownFileUploadFileMessage. - Screen readers read the native file input as a button whose name includes the last selected file, for example "File Upload: photo.jpg". This comes from the browser and can't be changed by the component.
FileUploadFileListhas no default name: give it one witharia-labelledbypointing to a visible heading.- Remove and retry buttons should include the file name in their accessible name (e.g. "Remove photo.jpg"), so they make sense out of context.
Focus management
- When a file is removed via
FileUploadRemoveFile, focus moves to a control in the next file item (or the previous one if it was last). If no files remain, focus returns to the file input, so it never drops to<body>. FileUploadTriggersetsaria-disabledon the<label>anddisabledon the input when the dropzone is disabled.
Status announcements
The component doesn't announce upload status on its own. The composition examples show the recommended pattern:
- Render one
role="status"/aria-atomic="true"live region that is always in the DOM, so it is registered before its content changes. - Announce every finished upload ("File uploaded successfully: photo.jpg." / "photo.jpg failed to upload."), combining files that finish together into one message.
- For uploads that can take longer than about 3 seconds, also announce when the upload starts and its progress (see Slow Upload Announcements). Focus never moves.
FileUploadMessageis not a live region: validation errors are read when the input is focused. If you need them announced immediately, send them to the same live region.
Progress
- Give each
ProgressRootanaria-labelthat includes the file name. For real progress, passvalue(Radix setsaria-valuenow/aria-valuetext); for an indeterminate animation, describe the state in the label instead. - Give
FileUploadFileMessageclassName="sr-only"when its text is redundant with visible UI, as shown in the default example.
Catalyst adds the visual styling and ARIA wiring; drag-and-drop and file selection come from react-dropzone.
Choosing the Right Component
Use File Upload when users need to select or drop one or more files with progress feedback. Reach for a different primitive when:
| If you need… | Use instead |
|---|---|
| A simple inline text or number input | TextField |
| Rich text or long-form input | TextArea |
| A date or calendar selection | Calendar |
Related
Combobox
A searchable dropdown component that combines an input field with a list of filterable options. Built with Radix UI primitives for robust accessibility.
Radio Group
A set of checkable buttons where no more than one can be checked at a time, providing single-choice selection in forms and interfaces.