A drop zone and button for choosing files, with type, size and count limits and a list of the chosen files you can remove from. The component picks the files; you upload them.
Up to 5 files, 5 MB each.
0 ready to upload
import { useState } from "react";
import { FileUpload } from "@rdloom/react";
export default function FileUploadBasicExample() {
const [files, setFiles] = useState<File[]>([]);
return (
<div className="w-[26rem] max-w-full">
<FileUpload label="Attachments" description="Up to 5 files, 5 MB each." maxFiles={5} maxSize={5 * 1024 * 1024} onChange={setFiles} />
<p className="mt-2 text-xs text-[var(--rd-color-text-muted)]">{files.length} ready to upload</p>
</div>
);
}Installation
npx rdloom add file-uploadCopies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.
Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/file-upload.jsonWorks, but without upgrade tracking.
Usage
import { FileUpload } from "@rdloom/react";
<div className="w-[26rem] max-w-full">
<FileUpload label="Attachments" description="Up to 5 files, 5 MB each." maxFiles={5} maxSize={5 * 1024 * 1024} onChange={setFiles} />
<p className="mt-2 text-xs text-[var(--rd-color-text-muted)]">{files.length} ready to upload</p>
</div>Images
PNG or JPEG, up to 2 MB each.
import { FileUpload } from "@rdloom/react";
// accept takes MIME types, wildcards and extensions. Check the files on your server too.
export default function FileUploadImagesExample() {
return (
<div className="w-[26rem] max-w-full">
<FileUpload label="Photos" description="PNG or JPEG, up to 2 MB each." accept={["image/png", "image/jpeg"]} maxSize={2 * 1024 * 1024} browseLabel="Choose photos" />
</div>
);
}Single
A PDF, up to 10 MB.
import { FileUpload } from "@rdloom/react";
// With multiple off, choosing a new file replaces the old one.
export default function FileUploadSingleExample() {
return (
<div className="w-[26rem] max-w-full">
<FileUpload label="Resume" description="A PDF, up to 10 MB." multiple={false} accept={[".pdf"]} maxSize={10 * 1024 * 1024} />
</div>
);
}API Reference
Defined by the spec. Components also accept the props of the React Aria component they wrap.
| Prop | Type | Default |
|---|---|---|
labelrequiredWhat the files are for, e.g. "Attachments". It names the control. | string | none |
descriptionHelp text, e.g. the limits. | string | none |
acceptAllowed types: MIME types like "image/png" or "image/*", or extensions like ".pdf". Others are rejected with a message. | readonly string[] | none |
multipleAllow more than one file. | boolean | true |
maxSizeLargest file in bytes. Bigger files are rejected. | number | none |
maxFilesMost files allowed in total. | number | none |
valueControlled list of chosen files. | readonly File[] | none |
defaultValueInitial files when uncontrolled. | readonly File[] | none |
onChangeCalled with the full list of files whenever it changes. | (files: File[]) => void | none |
onRejectCalled with files that were refused, and why. The component also lists them. | (rejections: { file: File; reason: "type" | "size" | "count" }[]) => void | none |
browseLabelText of the button that opens the file picker. | string | "Browse files" |
isDisabledPrevents choosing, dropping and removing. | boolean | false |
Accessibility
Role group, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.
Keyboard
- Tab reaches the Browse files button; Enter or Space opens the file picker
- Tab reaches each Remove button; Enter or Space removes that file
Screen readers announce
- The group is announced with its label and description
- Adding files announces how many were added, e.g. "2 files added"
- A rejected file is announced with its name and reason, e.g. "photo.tiff not added: file type not allowed"
- Removing announces the file removed
What your code must do
- Dragging is an extra: everything can be done with the Browse files button and the keyboard
- The group is named by its label and described by the description
- Each Remove button names its file (Remove report.pdf)
- Adding, removing and rejecting files are announced in a polite live region
- Rejected files and the reason are written out, not only coloured
- The drag-over highlight is also a change of text, not only of color
Guidelines
Use it when
- Attachments, imports, avatars and documents
- When people may have the files on their desktop to drag in
Avoid it when
- Uploading as a background process with progress: pair it with Progress and your own queue
- Picking one value from a list
Don't
- A drop zone with no button
- Limits that are only discovered after upload
- Trusting accept or maxSize alone: check files on the server too
Design tokens
The semantic tokens this component uses. Change them once and every component follows; see Design tokens.
--rd-color-surface-default--rd-color-surface-subtle--rd-color-surface-selected--rd-color-border-default--rd-color-border-strong--rd-color-action-primary--rd-color-text-default--rd-color-text-muted--rd-color-feedback-danger--rd-color-focus-ring--rd-radius-overlay--rd-radius-control