FileUpload
A dropzone over a native file input, with a list of the chosen files. Before any JavaScript runs, a click or a drop chooses files and a plain form submits them; hydrated, it checks each file against accept and maxSize and shows the upload state your code reports for each one.
Import
import { FileUpload } from '@nizivo/react/file-upload';
// Once per app (Getting started), after
// @nizivo/tokens/tokens.css and @nizivo/react/css/base.css and @nizivo/react/css/button.css and @nizivo/react/css/icon-button.css and @nizivo/react/css/inline-error.css and @nizivo/react/css/progress-bar.css
import '@nizivo/react/css/file-upload.css';Static or interactive
Client. FileUpload is a client component that reads its Field through FieldScope, so a page
with one also loads FieldScope and React Aria's drop hook. Its server HTML is complete and works
before hydration: a native <input type="file"> covers the dropzone, so a click opens the picker
and a file dropped on it is chosen natively, and a form submits the chosen files under name.
How to choose.
In a client app (a Vite or React Router single-page app, or a 'use client' file) it is an
ordinary React component: event handlers, refs and state work as usual.
In a client app.
The form submits the files
FileUpload never uploads anything. The native input holds the chosen files, and the component
writes the list back into it after every pick, drop and remove, so a form (a Server Action, or
method="post" with encType="multipart/form-data") submits exactly the files you see. With
multiple, each pick or drop adds to the list; without it, a new file replaces the old one.
A file that accept does not match, or that is larger than maxSize (in bytes; 1 MB is
1,000,000), is not added: its message appears under the dropzone, is read by screen readers and
describes the input. onReject receives the refused files. Check the files on the server too:
before hydration, the browser submits whatever was chosen.
Inside a Field, the Field's label names the input, its description and errors describe it, and
required is native: a submit with no file shows the Field's valueMissing message, and choosing
a file withdraws it. A click on the Field's label focuses the dropzone without opening the picker.
Uploading as files are chosen
When your code sends each file as soon as it is chosen, onFilesChange gives every held file with
an id the component assigned; key your requests by it, and pass each file's state back as items:
'use client';
const [items, setItems] = useState<FileUploadItem[]>([]);
<FileUpload
multiple
items={items}
onFilesChange={(files) => {
for (const { id, file } of files) startUpload(id, file, setItems);
}}
onRetry={(id) => retryUpload(id)}
onCancel={(id) => abortUpload(id)}
onRemove={(id) => deleteUpload(id)}
/>;An item is { id, status, progress, error }, with status uploading (a ProgressBar, determinate
when you pass progress, 0 to 100), complete or error (with a Retry button). While any item is
uploading, the dropzone's line shows how many are done. Cancel and Remove take a held file out of
the list and the input before they call you. An item whose id matches no held file is listed too,
after the held ones, so an edit form can show files uploaded earlier; give it a name.
Leave name off the FileUpload when you upload this way, or the form submits the files a second
time; submit your upload ids in your own hidden inputs.
Strings
Every string is in labels, with English defaults: prompt ({browse} marks where the brand
coloured word goes), browse, release (shown while files are dragged over), summary, the
rejection messages wrongType, tooLarge ({name}, {max}) and tooMany, and the list's
remove, retry, cancel, uploading, complete and failed. Sizes are formatted for
locale. Pass hint for the line under the prompt and icon for the mark above it.
Examples
Basic
Inside a Field, in a form that submits the files itself: a click or a drop on the zone chooses them, before hydration too, and the chosen files submit under name.
import type { JSX } from 'react';
import { Field } from '@nizivo/react/field';
import { FileUpload } from '@nizivo/react/file-upload';
/** Stands in for an `@nizivo/icons` glyph (Lucide's `upload`). */
function UploadMark(): JSX.Element {
return (
<svg
viewBox="0 0 24 24"
width="1em"
height="1em"
fill="none"
stroke="currentColor"
strokeWidth={1.92}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M12 3v12" />
<path d="m17 8-5-5-5 5" />
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
</svg>
);
}
export function FileUploadExample(): JSX.Element {
return (
<div style={{ maxInlineSize: 400 }}>
<Field
label="Product images"
required
messages={{ valueMissing: 'Add at least one product image.' }}
>
<FileUpload
name="images"
accept="image/png,image/jpeg,image/webp"
multiple
maxSize={20_000_000}
icon={<UploadMark />}
hint="PNG, JPG or WebP, up to 20 MB"
labels={{ prompt: 'Drop product images or {browse}' }}
/>
</Field>
</div>
);
}
States
The upload state your code passes back as items, one per file: uploading with its progress, complete, and failed with a retry. These items match no chosen file, as files uploaded earlier.
- packing-slip.pdf248 kB
- invoice-1042.pdf1.2 MBUploaded
- damaged-box.jpg3.4 MBThe connection dropped
import type { JSX } from 'react';
import { FileUpload } from '@nizivo/react/file-upload';
/** Stands in for an `@nizivo/icons` glyph (Lucide's `upload`). */
function UploadMark(): JSX.Element {
return (
<svg
viewBox="0 0 24 24"
width="1em"
height="1em"
fill="none"
stroke="currentColor"
strokeWidth={1.92}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M12 3v12" />
<path d="m17 8-5-5-5 5" />
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
</svg>
);
}
export function FileUploadStatesExample(): JSX.Element {
return (
<div style={{ maxInlineSize: 400 }}>
<FileUpload
aria-label="Order attachments"
multiple
icon={<UploadMark />}
hint="PDF or images, up to 10 MB"
items={[
{ id: 'a', name: 'packing-slip.pdf', size: 248_000, status: 'uploading', progress: 60 },
{ id: 'b', name: 'invoice-1042.pdf', size: 1_200_000, status: 'complete' },
{
id: 'c',
name: 'damaged-box.jpg',
size: 3_400_000,
status: 'error',
error: 'The connection dropped',
},
]}
/>
</div>
);
}
Disabled
Disabled: the zone takes no pick and no drop.
import type { JSX } from 'react';
import { Field } from '@nizivo/react/field';
import { FileUpload } from '@nizivo/react/file-upload';
/** Stands in for an `@nizivo/icons` glyph (Lucide's `upload`). */
function UploadMark(): JSX.Element {
return (
<svg
viewBox="0 0 24 24"
width="1em"
height="1em"
fill="none"
stroke="currentColor"
strokeWidth={1.92}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M12 3v12" />
<path d="m17 8-5-5-5 5" />
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
</svg>
);
}
export function FileUploadDisabledExample(): JSX.Element {
return (
<div style={{ maxInlineSize: 400 }}>
<Field label="Invoice" disabled description="Unlocks once the order is paid.">
<FileUpload name="invoice" accept=".pdf" icon={<UploadMark />} hint="PDF, up to 5 MB" />
</Field>
</div>
);
}
Props
className and style go on the root; every other input prop, ref included, goes on the native <input type="file">.
Client component Renders data-slot="file-upload" with the class nz-file-upload. Props: FileUploadProps. Source: src/file-upload/file-upload.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
accept | string | – | The native |
hint | ReactNode | – | The line under the prompt, such as "PNG, JPG or WebP, up to 20 MB". |
icon | ReactNode | – | Decorative mark above the prompt, such as your icon set's |
invalid | boolean | – | An error message is presented for this control (§8.3). Inside a Field, the Field sets it; an explicit value here wins. |
items | readonly FileUploadItem[] | – | Upload state per file, keyed by the ids |
labels | Partial<FileUploadLabels> | – | |
locale | string | 'en-US' | BCP 47 tag for sizes. |
maxSize | number | – | Bytes. A larger file is not added. 1 MB is 1,000,000 bytes, as |
multiple | boolean | false | More than one file; each pick or drop adds to the list. Without it, a new file replaces the old. |
onCancel | (id: string) => void | – | An item's cancel button, on an item |
onFilesChange | (files: FileUploadFile[]) => void | – | The held files changed: a pick, a drop, a remove, a cancel or the form's reset. Every held file, in order. |
onReject | (rejections: FileUploadRejection[]) => void | – | Files of a pick or drop that were not added; their messages show under the dropzone. |
onRemove | (id: string) => void | – | An item's remove button. A held file has already left the list and the input. |
onRetry | (id: string) => void | – | An item's retry button, on an item in |
- Also takes every attribute of
<input>exceptaccept,children,defaultValue,multiple,onChange,size,type,value. (Omit<ComponentPropsWithRef<"input">, "type" | "size" | "children" | "value" | "defaultValue" | "onChange" | "accept" | "multiple">)
Other exports
FileUploadFiletypeimport type { FileUploadFile } from '@nizivo/react/file-upload'A file the component holds (and its input submits), with the id the component gave it.
Name Type Default Description filerequiredFile– idrequiredstring– Stays the same while the file is listed. Key your
itemsand upload requests by it.FileUploadItemtypeimport type { FileUploadItem } from '@nizivo/react/file-upload'An item's upload state, from the app, which does the uploading. Matched to a held file by
id; an item whose id matches no held file is listed too, after the held files (a file uploaded earlier, shown on an edit form).Name Type Default Description errorstringlabels.failedThe message while
statusiserror.idrequiredstring– namestring– Shown in the list. Defaults to the held file's name; an item that matches no held file needs one, or its
idis shown.progressnumber– 0 to 100 while
uploading; absent, the bar is indeterminate.sizenumber– In bytes, shown beside the name. Defaults to the held file's size.
statusFileUploadStatus"uploading""complete""error"– FileUploadLabelstypeimport type { FileUploadLabels } from '@nizivo/react/file-upload'Strings FileUpload renders (§16.4): serialisable, English defaults.
{name}is a file's name,{max}the formattedmaxSize,{browse}where the browse word goes,{complete}and{total}counts.Name Type Default Description browserequiredstring'browse'The word that reads as the picker's button, in the brand colour.
cancelrequiredstring'Cancel'An item uploading.
completerequiredstring'Uploaded'A complete item's status.
failedrequiredstring'Upload failed'An item in error with no
errortext.listrequiredstring'Chosen files'The list's accessible name.
promptrequiredstring'Drop files here or {browse}'The dropzone's line.
releaserequiredstring'Release to add files'Replaces the prompt while a drag with files is over the dropzone.
removerequiredstring'Remove {name}'Each item's remove button.
retryrequiredstring'Retry'An item in error.
summaryrequiredstring'{complete} of {total} uploaded'Replaces the prompt while an item is uploading.
tooLargerequiredstring'{name} is larger than {max}.'A file over
maxSize.tooManyrequiredstring'{name} was not added: choose one file.'Without
multiple, every file of a drop after the first.uploadingrequiredstring'Uploading {name}'An uploading item's progress bar.
wrongTyperequiredstring'{name} is not an accepted file type.'A file
acceptdoes not match.FileUploadPropstypeimport type { FileUploadProps } from '@nizivo/react/file-upload'The props of
FileUpload, listed above.FileUploadRejectiontypeimport type { FileUploadRejection } from '@nizivo/react/file-upload'Name Type Default Description filerequiredFile– reasonrequiredFileUploadRejectionReason"type""size""count"– FileUploadRejectionReasontypeimport type { FileUploadRejectionReason } from '@nizivo/react/file-upload'Why a chosen or dropped file was not added.
type FileUploadRejectionReason = "type" | "size" | "count"FileUploadStatustypeimport type { FileUploadStatus } from '@nizivo/react/file-upload'Where an item's upload is. Absent on an item: chosen, not sent yet.
type FileUploadStatus = "uploading" | "complete" | "error"
Data attributes and slots
Style parts by class (.nz-<slot>) and state by data attribute. Attribute meanings come from contracts/vocabulary.json.
| Slot | Rendered by | Data attributes |
|---|---|---|
file-upload.nz-file-upload | FileUploadroot (no render) | none |
file-upload-browse.nz-file-upload-browse | FileUploadinner element (CSS only) | none |
file-upload-dropzone.nz-file-upload-dropzone | FileUploadinner element (CSS only); interactive, focus: nested-input; forced colours: outline on [data-drag-over] |
|
file-upload-hint.nz-file-upload-hint | FileUploadinner element (CSS only) | none |
file-upload-icon.nz-file-upload-icon | FileUploadinner element (CSS only) | none |
file-upload-input.nz-file-upload-input | FileUploadinner element (CSS only) | none |
file-upload-item.nz-file-upload-item | FileUploadinner element (CSS only) | none |
file-upload-item-actions.nz-file-upload-item-actions | FileUploadinner element (CSS only) | none |
file-upload-item-complete.nz-file-upload-item-complete | FileUploadinner element (CSS only) | none |
file-upload-item-error.nz-file-upload-item-error | FileUploadinner element (CSS only) | none |
file-upload-item-name.nz-file-upload-item-name | FileUploadinner element (CSS only) | none |
file-upload-item-size.nz-file-upload-item-size | FileUploadinner element (CSS only) | none |
file-upload-item-status.nz-file-upload-item-status | FileUploadinner element (CSS only) | none |
file-upload-list.nz-file-upload-list | FileUploadinner element (CSS only) | none |
file-upload-messages.nz-file-upload-messages | FileUploadinner element (CSS only) | none |
file-upload-prompt.nz-file-upload-prompt | FileUploadinner element (CSS only) | none |
CSS hooks
Set a hook on the component's class or any ancestor to restyle it without specificity games; leave it unset for the default shown.
| Hook | Default | Read as |
|---|---|---|
--nz-file-upload-bg |
|
|
--nz-file-upload-border |
|
|
--nz-file-upload-browse-fg | --nz-text-brand |
|
--nz-file-upload-gap | --nz-space-3 |
|
--nz-file-upload-hint-fg | --nz-text-secondary |
|
--nz-file-upload-icon-bg |
|
|
--nz-file-upload-icon-fg |
|
|
--nz-file-upload-item-bg | --nz-bg-surface |
|
--nz-file-upload-item-radius | --nz-radius-md |
|
--nz-file-upload-padding-block | --nz-space-6 |
|
--nz-file-upload-padding-inline | --nz-space-6 |
|
--nz-file-upload-radius | --nz-radius-xl |
|
Layers: nz.components. Classes: .nz-file-upload, .nz-file-upload-browse, .nz-file-upload-dropzone, .nz-file-upload-hint, .nz-file-upload-icon, .nz-file-upload-input, .nz-file-upload-item, .nz-file-upload-item-actions, .nz-file-upload-item-complete, .nz-file-upload-item-error, .nz-file-upload-item-name, .nz-file-upload-item-size, .nz-file-upload-item-status, .nz-file-upload-list, .nz-file-upload-messages, .nz-file-upload-prompt, .nz-glyph, .nz-progress-bar.
Tokens this stylesheet reads (38)
--nz-bg-brand, --nz-bg-brand-subtle, --nz-bg-disabled, --nz-bg-subtle, --nz-bg-surface, --nz-border-brand, --nz-border-control-hover, --nz-border-critical, --nz-border-strong, --nz-border-subtle, --nz-border-width-default, --nz-duration-instant, --nz-easing-standard, --nz-font-body-m, --nz-font-body-s, --nz-font-body-s-line-height, --nz-font-label-m, --nz-font-label-m-line-height, --nz-font-label-m-strong, --nz-icon-primary, --nz-radius-full, --nz-radius-md, --nz-radius-xl, --nz-size-control-md, --nz-size-icon-md, --nz-size-icon-sm, --nz-space-0-5, --nz-space-1, --nz-space-2, --nz-space-3, --nz-space-6, --nz-text-brand, --nz-text-critical, --nz-text-disabled, --nz-text-on-brand, --nz-text-primary, --nz-text-secondary, --nz-text-success
Accessibility
These parts are interactive. Each gets the focus outline and touch-action rule from @nizivo/react/css/base.css (the nz.base layer), so the floor survives a restyle:
file-upload-dropzone: draws the outline when the hidden input inside it has keyboard focus.
In forced-colours mode these states keep a visible system colour: file-upload-dropzone[data-drag-over] (outline).
Keyboard
| Keys | Action |
|---|---|
| Tab | Moves focus to the dropzone's file input; the dropzone draws the focus outline. Tab again moves through each listed file's buttons (Retry, Cancel, Remove) in order. |
| Enter, Space | On the dropzone, opens the platform's file picker (the native input's own behaviour). On a list button, presses it. |
| Enter, Space on Remove or Cancel | Takes the file out of the list and the input; focus moves to the file that took its place, else the one before, else the dropzone. |
Source: A native <input type="file"> (file-upload.client.tsx); react-aria 3.53.1 useDrop through _engines/react-aria/file-upload.client.tsx for the drop target only. Dropping is pointer-only; the input is the keyboard and screen-reader path. Checked by the browser lane (test/browser/file-upload.spec.tsx) in Chromium.
Figma
Node ids from design/component-map.json, which nz gen writes from component.json. The links open the Nizivo Figma library, which may ask you to sign in.
- Canonical: Figma node
7275:11548 - Ghost masters: none
Fieldset
A native fieldset and legend for related controls, such as a row of switches or checkboxes, with a shared description and error. Disabling it disables every control inside, with no client code.
Form
An optional client <form> for forms whose errors come back from the server. Errors keyed by Field name appear in each Field, focus moves to the first invalid control, and an optional error summary links to each one. SubmitButton is pending while the form's action runs; FormMessage is the form-level message.