File Upload
Drag-and-drop file upload with file list, progress, validation, four visual variants, five sizes, intent colours, and per-file status (idle/uploading/success/error).
Playground
Installation
pnpm add @tessinaui/uiUsage
import { FileUpload, FileUploadItem, FileUploadList } from "@tessinaui/ui";{/* Single file */}
<FileUpload onValueChange={(files) => console.log(files)} />
{/* Multiple files */}
<FileUpload multiple maxFiles={5} />
{/* Restrict to images, 5 MB max */}
<FileUpload accept="image/*" maxSize={5 * 1024 * 1024} />
{/* Custom title / description / button */}
<FileUpload
title="Front of your ID document"
description="Drag and drop a file less than 5MB"
actionLabel="Or select file"
/>
{/* Variants */}
<FileUpload variant="card" />
<FileUpload variant="dashed" />
<FileUpload variant="filled" />
<FileUpload variant="minimal" />
{/* Intents */}
<FileUpload intent="error" />
<FileUpload intent="success" />
{/* States */}
<FileUpload loading />
<FileUpload disabled />
<FileUpload skeleton />
{/* Controlled */}
<FileUpload value={files} onValueChange={setFiles} />
{/* Rejected file callback */}
<FileUpload
maxSize={1024 * 1024}
onFileRejected={(r) => alert(r.message)}
/>Showcase
Variants
| Variant | Description |
|---|---|
card | Solid filled card surface — best for prominent primary upload zones |
dashed | Dashed border drop-zone — the classic "drop files here" look |
filled | Filled background with a solid border — works well with intent tinting |
minimal | Solid 1px border, transparent background — discreet inline upload |
Props
<FileUpload>
| Prop | Type | Default | Description |
|---|---|---|---|
accept | string | — | Comma-separated MIME types or extensions (e.g. "image/*,.pdf") |
maxSize | number | — | Max bytes per file |
maxFiles | number | 1 | Max files in the list |
multiple | boolean | maxFiles > 1 | Whether the file picker allows multi-select |
value | UploadedFile[] | — | Controlled file list |
defaultValue | UploadedFile[] | [] | Uncontrolled initial list |
onValueChange | (files) => void | — | Fires when the list changes |
onFileAdded | (file) => void | — | Fires per accepted file |
onFileRejected | (rejection) => void | — | Fires per file that fails validation (size / type / max-files) |
onFileRemoved | (file) => void | — | Fires when a file is removed |
variant | card | dashed | filled | minimal | dashed | Visual style |
size | xs | sm | md | lg | xl | md | Padding / typography scale |
intent | none | error | warning | success | info | none | Colour intent |
rounded | none | sm | md | lg | full | lg | Corner radius |
disabled | boolean | false | Disables the dropzone and picker |
readOnly | boolean | false | Disables add/remove but renders normally |
loading | boolean | false | Shows a spinner inside the dropzone with "Uploading…" |
skeleton | boolean | false | Renders a pulse placeholder version |
hideList | boolean | false | Hide the file list rendered below |
icon | ReactNode | <Upload /> | Custom icon inside the circular badge |
title | ReactNode | — | Heading text |
description | ReactNode | — | Subtitle text |
actionLabel | ReactNode | "Or select file" | Button label |
dir | ltr | rtl | ltr | Text direction |
<FileUploadItem>
| Prop | Type | Description |
|---|---|---|
file | UploadedFile | The file metadata + status |
size | FileUploadSize | Inherits from parent unless overridden |
rounded | FileUploadRounded | Inherits from parent unless overridden |
onRemove | () => void | Remove button handler — pass undefined to hide it |
onRetry | () => void | Retry button handler — only visible in error status |
UploadedFile
{
id: string;
file: File;
name: string;
size: number;
type: string;
preview?: string; // object URL for images
progress?: number; // 0–100
status?: "idle" | "uploading" | "success" | "error";
error?: string;
}File item states
The status of each entry in the list drives its appearance:
idle— file picked but no upload started; name + size + remove buttonuploading— animated progress bar, percentage, and a spinner instead of the remove buttonsuccess— green check + "Uploaded"; remove still allowederror— red alert + error message; retry button appears whenonRetryis provided
Validation
FileUpload validates each incoming file against accept and maxSize. Rejected files don't enter the list — they fire onFileRejected with a { file, reason, message } payload where reason is one of:
"type"— extension or MIME type didn't matchaccept"size"— file is larger thanmaxSize"max-files"— adding would exceedmaxFiles
Accessibility
- The drop-zone is a
role="button"withtabIndex={0}and a meaningfularia-label - Enter / Space opens the native file picker
- Each item exposes
aria-label="Remove file"/"Retry upload"on its action buttons - Object URLs created for image previews are revoked on unmount and on file removal
OTP Input
One-time password input with individual digit cells. Supports three visual variants, five sizes, five intents, masking, separator groups, numeric/alphanumeric/alphabetic input types, and full RTL/LTR support.
Calendar
Date picker calendar built on react-day-picker v9. Supports single, range, and multiple selection modes, RTL, week numbers, disabled dates, multi-month view, and an optional footer slot.