Components
File Upload
People add files by dropping them on the drop zone or browsing. Each file gets a row with its name, size and progress; it can be cancelled while it uploads and removed when it's done. In Arabic the rows mirror and progress fills from the right.
Preview
Mirrored is the same English content laid out right to left: it separates a mirroring bug from a translation one.
Installation
npx ritla add file-uploadAlso works with shadcn: npx shadcn@latest add @ritla/file-upload
Dependencies it brings: @base-ui/react, @phosphor-icons/react
Installing by hand copies the component's source into your project. The source comes with a Ritla UI license, so the CLI is the short way to the same files.
Usage
import { FileUpload, FileUploadItem, FileUploadList } from "@/components/ui/file-upload";<FileUpload
label="Receipts"
accept=".pdf,.jpg"
maxSize={10_000_000}
helperText="PDF or JPG, up to 10 MB each"
onFilesAdd={(accepted, rejected) => startUploads(accepted, rejected)}
>
<FileUploadList>
{files.map((file) => (
<FileUploadItem
key={file.id}
name={file.name}
size={file.size}
status={file.status}
progress={file.progress}
onCancel={() => cancel(file.id)}
onRemove={() => remove(file.id)}
/>
))}
</FileUploadList>
</FileUpload>Examples
Row states
While uploading the X cancels (status="uploading", progress 0 to 100, or null when unknown). When done the Trash removes and the bar is full. On failure the reason shows under the name, with Try again (onRetry). disabled dims the zone and the rows.
One file, and the camera on phones
multiple={false} takes one file, and capture="environment" opens the back camera on phones, where there is no dragging. The file name stays as its owner wrote it, in any language.
API reference
FileUpload
| Prop | Type | Default | Description |
|---|---|---|---|
label | ReactNode | None | The label above the drop zone, named after the document you want ("Receipts"). |
required | boolean | false | The asterisk after the label. |
helperText | ReactNode | None | Under the zone: the allowed types, size and count, before anyone tries. |
error | ReactNode | None | An error for the whole upload ("Add at least one receipt"), in red in place of the helper text. |
accept | string | None | Allowed types as in <input accept>: .pdf,.jpg or image/*. Every file is checked, dropped or picked. |
maxSize | number | None | Largest file, in bytes. |
maxFiles | number | None | Most files in the list (failed rows don't count). |
multiple | boolean | true | More than one file at a time. |
capture | "environment" | "user" | None | Opens the camera on phones instead of the file picker. |
disabled | boolean | false | State = Disabled. |
name | string | None | The file input's name, for a form that posts files itself. |
onFilesAdd | (accepted: File[], rejected: FileRejection[]) => void | None | Picked or dropped files after the checks: start uploading accepted and show each rejected file as a row with its reason (type, size or count). Nothing uploads by itself. |
dropText | ReactNode | None | The zone's line. Default: Figma's text in the page language. |
browseLabel | ReactNode | None | The button's label. Default: "Browse files" (Arabic «اختيار ملفات», or «اختيار ملف» for one file). |
FileUploadItem
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | None | The file's own name, never translated or transliterated. |
size | number | None | Size in bytes, shown in the page's language and numerals. |
status | "uploading" | "done" | "error" | "uploading" | Sets the row's button: the X cancels while uploading, the Trash removes after. |
progress | number | null | null | Progress 0 to 100 while uploading; null for the striped bar when it isn't known. |
error | ReactNode | None | Why it failed, in plain words ("The file is larger than 10 MB"). |
onCancel / onRemove | () => void | None | The row's button. After a removal, focus moves to the next row's button, or the previous one, or Browse. |
onRetry | () => void | None | Shows Try again on a failed row. |
disabled | boolean | false | State = Disabled. |
showFileSize / showIcon | boolean | true | Figma's Show file size and Show icon. |
Accessibility
Keyboard
Keys are written for left-to-right. In right-to-left the arrow keys swap: the one pointing toward the reading direction moves forward.
| Key | Action |
|---|---|
| Tab | To Browse, then to each row's button. |
| Enter | On Browse, opens the file dialog; on a row's button, cancels or removes. |
| Space | The same as Enter. |
ARIA
- The drop zone is only a shortcut: Browse is a real button that opens a native file input, so everything works with a keyboard, a screen reader and on phones.
- The whole upload is a group named by its label; Browse is read with the label and described by the helper text, so a screen reader hears the types and limits before choosing.
- Each row's button carries the file name: "Cancel upload of hotel-invoice.pdf", "Remove taxi-receipt.jpg".
- A polite live region announces when a file is done or failed, not every percent. Each bar is
role="progressbar", named by its file.
Arabic and RTL notes
Mirrors
- The rows: the type icon at the start (the right), then the name and size, the button at the end (the left).
- The progress bar fills from the inline start, so from the right.
Does not mirror
- The type icon doesn't mirror, and its extension stays Latin (PDF, JPG).
- The file name stays as written, isolated with
dir="auto"; it is never translated or transliterated. - The cloud, Trash and X icons don't mirror.
- Numerals
- The size follows
numerals, with its unit in the page language: "2.3 MB" in English and "2.3 ميغابايت" or "٢٫٣ ميغابايت" in Arabic. - Arabic typography
- The name is
body-16Medium and the sizebody-12, with the taller Arabic line height; a long name is cut with an ellipsis rather than wrapping.