File upload
Accessible file selection, validation, and controlled upload cards.
No files leave your browser. Choose a PDF or CSV, then simulate the result.
Usage
import {
FileUpload,
type UploadFileItem,
} from '@commenda/pathfinder/file-upload';
<FileUpload
label="Supporting documents"
files={files}
onFilesAccepted={startUploads}
validationConfig={{
accept: { 'application/pdf': ['.pdf'], 'text/csv': ['.csv'] },
maxSizeBytes: 10_000_000,
maxFiles: 5,
}}
actionConfig={{
onCancel: cancelUpload,
onRetry: retryUpload,
onRemove: removeFile,
onPreview: previewFile,
onDownload: downloadFile,
}}
/>;The first example keeps files in memory and never uploads them. Pick a PDF or CSV, then use Complete uploads or Fail uploads to simulate a result. Retry returns a failed card to uploading. Its Code tab includes all state and handlers; replace the simulation with your upload transport.
Append accepted files to controlled files immediately, with stable IDs and upload: { status: 'uploading', progress: 0 }. Start your upload in onFilesAccepted; update progress from actual transport events and set complete only after the server confirms success. Retain the original File objects in your application for retries. Keep completed and failed cards in selection order, newest last.
Compact button
Use the button where a dropzone would compete with the form. Limits remain visible beside it and wrap on narrow screens. The dropzone itself is a single native button: its entire area opens the file picker with pointer, Enter, or Space; “browse” is styled text, not a nested button.
Single file
validationConfig.maxFiles: 1 replaces the picker with the selected file's card. Cancel or remove that card to choose another file. Supply those callbacks to allow replacement. Multiple-file mode keeps the picker visible; exceeding the limit reports a rejection. Existing uploading, failed, and completed items all count toward the limit.
Upload states and recovery
- Board resolution, signed.pdf1.2 MBUploading 64%
- Q3 2026 sales tax return.pdf1.2 MBUploaded
- Nexus study, California.csvUpload failed. Retry to try again.
Uploading shows progress and Cancel. Completed files offer Preview, Download, and Remove when those callbacks exist. Failed files retain their filename and error with Retry and Remove. Unknown progress is indeterminate; 100% alone does not mean success. No operation blocks the surrounding page. Removing a focused action restores focus to an available control within the uploader.
Validation and errors
Choose a file smaller than 10 MB.
Empty files are always rejected. An accept entry matches its MIME type (including a wildcard such as image/*) or one of its extension fallbacks, case-insensitively. Extensions should include their leading dot. A file exactly at maxSizeBytes is accepted. Use positive finite size limits and positive integer file limits.
Validation processes selected files in order: valid files fill remaining capacity; rejected files never enter onFilesAccepted. onFilesRejected receives { file, code, message } for each rejection, with codes empty, type, size, or count. Re-selecting the same file works because the native input is reset. Duplicate filenames are not deduplicated: assign distinct IDs or apply your product's duplicate policy in the host.
Limits are generated from validationConfig; description can provide localized copy. Built-in error messages are English; use onFilesRejected with errorConfig for application translations. Browser MIME/extension validation is a convenience, not content verification. The server still enforces size, content, permissions, and scanning. No credential, company ID, storage URL, or API client enters this component.
Disabled state
You have read-only access.
1 uploading, 1 uploaded, 1 failed.- Board resolution, signed.pdf1.2 MBUploading 64%
- Q3 2026 sales tax return.pdf1.2 MBUploaded
- Nexus study, California.csvUpload failed. Retry to try again.
Disabled state prevents file acceptance and card actions. Use a reason to explain unavailable access. Long original filenames truncate visually with a native full-name tooltip; error messages wrap. File selection, progress, and failure are announced through status changes without announcing every progress percentage.
API reference
| Prop | Type | Behavior |
|---|---|---|
labelRequired | string | Required accessible field name |
hideLabel | boolean | Visually hide the field label |
variant | "button"|"dropzone" | Picker presentation; defaults to dropzone |
filesRequired | readonly UploadFileItem[] | Controlled list, in host-provided order |
onFilesAcceptedRequired | (
files: readonly File[]
) => void | Receive validated files and start host uploads |
onFilesRejected | (
rejections: readonly FileRejection[]
) => void | Report client-side validation failures |
validationConfig | FileValidationConfigView fields | Optional type, size, and count limits |
actionConfig | FileActionConfigView fields | Cancel, retry, remove, preview, download callbacks by ID |
disableConfig | ObjectView fields | Disable selection and card actions |
description | string | Override generated visible limits |
errorConfig | ObjectView fields | Host error overrides client validation copy |
className | string | Host width/layout; no fixed 480px component width |
validationConfigObject fields
| Field | Type | Requirement |
|---|---|---|
accept | Readonly<Record<string, readonly string[]>> | Optional MIME types with extension fallbacks, e.g. {'application/pdf': ['.pdf']}. |
maxSizeBytes | number | Optional |
maxFiles | number | Optional Counts all controlled items, including uploading and failed files. One swaps the picker for its card. |
actionConfigObject fields
| Field | Type | Requirement |
|---|---|---|
onCancel | ((id: string) => void) | Optional |
onRetry | ((id: string) => void) | Optional |
onRemove | ((id: string) => void) | Optional |
onPreview | ((id: string) => void) | Optional |
onDownload | ((id: string) => void) | Optional |
disableConfigObject fields
| Field | Type | Requirement |
|---|---|---|
isDisabled | boolean | Required |
reason | string | Optional |
errorConfigObject fields
| Field | Type | Requirement |
|---|---|---|
message | string | Required |
Design rules and integration
- FILE-UPLOAD-01: One dropzone per form where space permits; use a button in compact layouts. Show limits before selection. Radius 8px, dashed Pearl 400 border, 24px zone padding, and 6px content gaps follow the rendered frame.
- FILE-UPLOAD-02: Accept valid files immediately and append newest last. Reject invalid files before inserting cards; preserve failed uploads for retry.
- FILE-UPLOAD-03: The application owns files, progress, upload cancellation, preview/download, and removal persistence. Update controlled files synchronously when accepting a batch; guard concurrent transport operations and ignore late progress after cancel/removal.
- FILE-UPLOAD-04: Full-zone keyboard/pointer activation; restore focus on picker cancellation and card removal. Drag-over tint takes 150ms, inserted cards fade/translate 4px for 200ms, and the existing ProgressBar interpolates for 300ms. Reduced motion removes these transitions.
Kakashi's MultiFileUploader supplies useful accept, size/count, preview, and upload callback patterns. Its company/authentication props, S3 helper, and signed-URL viewer stay application-owned. Map FileBasic.fileName to filename, retain IDs separately, and map existing upload results to upload.status. No consumer migration is performed. AI Attachment remains a compact message/composer presentation; FileCard is the form upload lifecycle row. UploadRequestCard can continue its existing integration without an API change.