Pathfinderby Commenda
Components

File upload

Accessible file selection, validation, and controlled upload cards.

Supporting documents

No files leave your browser. Choose a PDF or CSV, then simulate the result.

Usage

Example.tsx
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

Supporting documents
PDF, CSV · up to 10 MB per file · maximum 5 files

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

Supporting documents

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

Supporting documents
1 uploading, 1 uploaded, 1 failed.
  • Board resolution, signed.pdf1.2 MBUploading 64%
    x
  • 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

Supporting documents

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

Supporting documents

You have read-only access.

1 uploading, 1 uploaded, 1 failed.
  • Board resolution, signed.pdf1.2 MBUploading 64%
    x
  • 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

PropTypeBehavior
labelRequiredstringRequired accessible field name
hideLabelbooleanVisually hide the field label
variant"button"|"dropzone"Picker presentation; defaults to dropzone
filesRequiredreadonly UploadFileItem[]Controlled list, in host-provided order
onFilesAcceptedRequired( files: readonly File[] ) => voidReceive validated files and start host uploads
onFilesRejected( rejections: readonly FileRejection[] ) => voidReport client-side validation failures
validationConfigFileValidationConfigView fieldsOptional type, size, and count limits
actionConfigFileActionConfigView fieldsCancel, retry, remove, preview, download callbacks by ID
disableConfigObjectView fieldsDisable selection and card actions
descriptionstringOverride generated visible limits
errorConfigObjectView fieldsHost error overrides client validation copy
classNamestringHost width/layout; no fixed 480px component width
validationConfigObject fields
FieldTypeRequirement
acceptReadonly<Record<string, readonly string[]>>Optional

MIME types with extension fallbacks, e.g. {'application/pdf': ['.pdf']}.

maxSizeBytesnumberOptional
maxFilesnumberOptional

Counts all controlled items, including uploading and failed files. One swaps the picker for its card.

actionConfigObject fields
FieldTypeRequirement
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
FieldTypeRequirement
isDisabledbooleanRequired
reasonstringOptional
errorConfigObject fields
FieldTypeRequirement
messagestringRequired

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.

On this page