File input
PortedA button that opens the file picker (or a box to drop files on), and the list of chosen files, each with a remove button. It is always in a Field, which shows why a file was not added. Native <button> attributes and ref pass through to the button.
Résumé
- ana-ruiz-resume.pdf243 KB
import { useState } from "react";
import { FileInput } from "@etched/monet/file-input";
export function Preview() {
const [files, setFiles] = useState(() => [new File([new Uint8Array(248_000)], "ana-ruiz-resume.pdf", { type: "application/pdf" })]);
return (
<div className="grid">
<FileInput label="Résumé" accept=".pdf,.docx,.txt" value={files} onValueChange={setFiles}>
Upload résumé
</FileInput>
</div>
);
}Installation
import { FileInput } from "@etched/monet/file-input";Examples
Résumé
One file: a new pick replaces it. accept and maxSize check each pick; a file that fails is not added, and the field shows why. The app holds the list in value.
Résumé
PDF, DOCX or text, up to 10 MB.
import { useState } from "react";
import { FileInput } from "@etched/monet/file-input";
const MB = 1024 * 1024;
export function Resume() {
const [files, setFiles] = useState<File[]>([]);
return (
<div className="grid">
<FileInput label="Résumé" accept=".pdf,.docx,.txt" maxSize={10 * MB} description="PDF, DOCX or text, up to 10 MB." value={files} onValueChange={setFiles}>
Upload résumé
</FileInput>
</div>
);
}Drop zone
dropzone adds a box to drop files on. With multiple, each pick or drop adds to the list; maxFiles caps it, and each file past the cap gets its own message.
Candidate files
Drop files here, or
Up to 3 files, 10 MB each.
import { FileInput } from "@etched/monet/file-input";
const MB = 1024 * 1024;
export function DropZone() {
return (
<div className="grid">
<FileInput label="Candidate files" multiple dropzone maxFiles={3} maxSize={10 * MB} description="Up to 3 files, 10 MB each.">
Choose files
</FileInput>
</div>
);
}In a form
With name, a native form submit sends the files; required stops a submit while no file is chosen.
import { useState } from "react";
import { Button } from "@etched/monet/button";
import { FileInput } from "@etched/monet/file-input";
export function InAForm() {
const [sent, setSent] = useState("");
return (
<form
className="grid"
onSubmit={(event) => {
event.preventDefault();
const files = new FormData(event.currentTarget).getAll("attachment");
setSent(`Sent ${files.length} file${files.length === 1 ? "" : "s"}.`);
}}
>
<FileInput label="Signed offer" name="attachment" required accept=".pdf">
Choose file
</FileInput>
<div className="row">
<Button type="submit" variant="primary">
Send
</Button>
<span role="status">{sent}</span>
</div>
</form>
);
}Optional and disabled
Cover letter (optional)
Offer letter
Drop a file here, or
- offer-letter-ana-ruiz.pdf178 KB
import { FileInput } from "@etched/monet/file-input";
const offer = new File([new Uint8Array(182_000)], "offer-letter-ana-ruiz.pdf", { type: "application/pdf" });
export function OptionalAndDisabled() {
return (
<div className="grid">
<FileInput label="Cover letter" required={false} accept=".pdf">
Upload cover letter
</FileInput>
<FileInput label="Offer letter" value={[offer]} dropzone disabled>
Upload offer letter
</FileInput>
</div>
);
}Accessibility
- The button and each remove button are native buttons:
EnterandSpacepress them. The button opens the system file picker. - The button is named by the field's
labeland its own text, such as "Résumé Upload résumé". Each remove button is named "Remove" and the file name, or byremoveLabel. - Screen readers hear "Added" with the file names, or why a file was refused, through the component's own polite live region.
- A refusal takes the place of
descriptionin the button'saria-describedby, and the button getsaria-invalid. - After a remove, focus goes to the next remove button, else the previous one, else the button. An empty
requiredsubmit, in aFormtoo, focuses the button. - Give it a
label, so the button says what the file is for. The drop zone is for pointers only; the button is the keyboard path.
API reference
| Prop | Type | Default | Description |
|---|---|---|---|
| label | ReactNode | – | Visible label. Without one, give the control an aria-label. |
| labelTooltip | ReactNode | – | More about the field, behind an info button after the label. |
| labelTooltipLabel | string | "More information" | Names the info button. |
| description | ReactNode | – | Help text below the control. Hidden while an error shows. |
| error | FieldError | – | |
| value | File[] | – | The chosen files, when the app controls them. |
| defaultValue | File[] | [] | The first files, when the input keeps its own list. |
| onValueChange | (value: File[]) => void | – | Called with the new list after a pick, a drop or a remove. Files that fail a check are left out. |
| accept | string | – | The file types allowed, as on a native input: ".pdf,.docx" or "image/*". The picker offers only these, and a file of another type is not added. |
| multiple | boolean | – | Each pick adds to the list (a file already in it is not added again). Without it, a pick replaces the file. |
| maxSize | number | – | The largest file allowed, in bytes. A larger file is not added. |
| maxFiles | number | – | The most files the list holds, with multiple. Files past it are not added. |
| dropzone | boolean | – | A box the user can drop files on, around the button. Without it, there is only the button. |
| name | string | – | The form field name: a native form submit sends the files under it. |
| required | boolean | – | true stops a form submit while no file is chosen; false adds the optional marker after the label. |
| disabled | boolean | – | |
| removeLabel | (file: File) => string | `Remove ${file.name}` | Names a file's remove button. |
| typeMessage | (file: File) => string | `${file.name} is not an accepted file type (.pdf, .docx).` | Shown when a file's type is not in accept. |
| sizeMessage | (file: File) => string | `${file.name} is larger than 5 MB.` | Shown when a file is larger than maxSize. |
| limitMessage | (file: File) => string | `${file.name} was not added: the limit is 3 files.`, or `…: choose one file.` | Shown when a file would go past maxFiles, or past one file without multiple. |
| missingMessage | string | "Choose a file.", or "Choose at least one file." with `multiple` | Shown when a submit finds no file and the input is required. |
| addedLabel | (files: File[]) => string | `Added ${names}` | What screen readers hear when files are added. |
| dropLabel | string | "Drop a file here, or", or "Drop files here, or" with `multiple` | The text before the button in a dropzone. |
| children | ReactNode | "Choose file", or "Choose files" with `multiple` | The button's text. |
| className | string | – | Class on the outer element, for layout. |