Upload
Composable file picker, dropzone, status, and toasts for one useUpload hook.
The upload family renders one useUpload hook.
Upload holds the hook, and the parts inside it read it: a button that opens
the file picker, a dropzone with the progress inside it, and the status of
the queue. UploadToasts reports on the same hook from anywhere in the tree.
For a form value, use UploadField.
Preview
Drop files on the dropzone or click it. Progress shows inside the dropzone.
Drag and drop files here
You can upload 4 files. Each up to 75.0 MB.
Install
Complete UI Setup first: the stylesheet and one
<Toaster />. Then add this component:
import {
Upload,
UploadDropzone,
UploadDropzoneTrigger,
UploadFileItem,
UploadFileList,
UploadStatus,
UploadToasts,
UploadTrigger,
usePasteFiles,
useUploadHints,
} from "@dimah-s3/ui";Usage
"use client";
import { useUpload } from "@dimah-s3/react";
import { Upload, UploadDropzone, UploadToasts } from "@dimah-s3/ui";
export function Attachments() {
const upload = useUpload({ route: "attachments", maxFiles: 5, queue: true });
return (
<>
<Upload upload={upload}>
<UploadDropzone />
</Upload>
<UploadToasts upload={upload} />
</>
);
}Upload renders the hidden file input and no element of its own, so place
its parts in your own layout. One hook can have several parts: a button in a
toolbar and a dropzone below it share the same queue.
| Part | Renders |
|---|---|
UploadTrigger | A Button that opens the file picker |
UploadDropzone | A drop area. By default, a trigger with the route's limits and the status |
UploadDropzoneTrigger | The part of a dropzone you click, or focus and press Enter, to pick files |
UploadStatus | One card for one file, or a summary above the file list for several |
UploadFileList | Every file in the queue, as rows or a grid. A function child renders each |
UploadFileItem | The card for one file, with its progress, error, and cancel or retry |
UploadToasts | Progress, results, and rejected files as toasts. It renders no element |
Examples
Button
UploadTrigger with UploadStatus under it. The status renders nothing
until a file is picked.
Button styles
UploadTrigger is a Button: variant, size, and every other prop are
forwarded, and children replace the icon and label. An icon-only trigger
needs an aria-label. tooltip adds a tooltip. All the triggers below share
one hook and one status.
Image grid
Give UploadDropzone children to arrange it yourself. Here the trigger has
its own text, and UploadStatus layout="grid" shows the images as tiles
inside the dropzone. The route's limits still describe the trigger for
screen readers.
Add photos to the album
Drop up to 8 images, or click to choose them.
Compact dropzone
A one-line dropzone for a form, with the status outside it. useUploadHints
returns the route's limits as text, the same lines the default dropzone
shows.
Custom file rows
Pass a function to UploadFileList to render each file yourself. It gets one
entry of upload.files with its phase, progress, and error.
Paste
usePasteFiles uploads files pasted anywhere on the page. It leaves pastes
into form fields and editable text alone, and pastes a focused dropzone
already took.
Copy an image or a screenshot, then press Ctrl + V anywhere on this page.
Disabled
Pass disabled to useUpload. Every part follows it: the trigger and the
dropzone disable, and drops and pastes do nothing.
Drag and drop files here
You can upload 2 files. Each up to 75.0 MB.
Dropzone
UploadDropzone takes files dropped anywhere on it, also over the status
inside it. Its default content is an UploadDropzoneTrigger and an
UploadStatus:
<UploadDropzone />
// is the same as
<UploadDropzone>
<UploadDropzoneTrigger />
<UploadStatus />
</UploadDropzone>Pass children to change that. Leave out UploadStatus to render it
somewhere else, or give it layout="grid".
<UploadDropzone className="min-h-48">
<UploadDropzoneTrigger>
<p className="text-sm font-medium">Drop photos here</p>
</UploadDropzoneTrigger>
</UploadDropzone>
<UploadStatus />UploadDropzoneTrigger is a button: a click, Enter, or Space opens the
picker, and files pasted while it has focus upload. Its default content is
an icon, a title, and the route's size and type limits. children replace
them; the limits stay attached as its description. Do not put buttons or
links inside the trigger. Put them next to it, in the dropzone.
While files are dragged over it, the dropzone sets data-drag-active, and
data-drag-reject when they cannot be dropped. The default title says which,
and a polite live region announces it. The dropzone sets data-disabled
while the hook is disabled or the root is full.
The trigger and the dropzone stay enabled while files upload. New files join
the queue when the hook has queue: true, and replace it otherwise. While
the root is full, as in a full UploadField,
"Maximum files reached." leads the trigger's description.
Status
UploadStatus renders nothing while the hook is idle. One file shows as its
own card. Several files show as one card: a summary with the overall
progress ring, the count, and the time left, then a row for each file. The
summary has "Cancel all uploads" while they upload, and a dismiss action once
they settle, with "Retry failed uploads" when some failed. Each failed file
has its own retry action, which turns into its cancel action as the file goes
back in flight, so focus stays on it. The rows scroll past 20rem.
Pause appears while files upload when the hook has an uploadStore, so the
uploads can resume. It sets data-phase to the
hook's phase.
layout="grid" shows the files as tiles, with the summary above them, on
both UploadStatus and UploadFileList.
When cancel, pause, dismiss, or remove takes away the card that has focus, focus moves to the next action in the same list, then the previous one, then the root's trigger or dropzone.
Toasts
Parts never toast. Mount UploadToasts once per hook, and one <Toaster />
in your root layout (UI Setup).
- While files upload, a progress toast with a cancel action.
- Once the queue settles, the result. A failure has a Retry action.
- For each call that rejects files, the reason, formatted from the error code.
A second UploadToasts for the same hook renders nothing and warns in
development. Inside an ObjectList
your UploadToasts replaces the list's built-in toasts.
Call options
call on UploadTrigger and UploadDropzone applies to the files they take
in. Its uploadOptions merge over the hook's, and maxFiles lowers the
hook's limit for that call.
<UploadTrigger
call={{ uploadOptions: { metadata: { album: albumId } } }}
tooltip="Add photos"
/>Inside an ObjectList with the same hook, the source's uploadOptions for
the open folder apply first, so files land in that folder. Your call merges
over them.
Your own parts
useUploadContext() returns the upload of the nearest Upload and its
isFull flag. Pass isFull to Upload (as a full UploadField does) to
render UploadTrigger and UploadDropzoneTrigger focusable but
aria-disabled; it changes only how they render, so the upload must take
no files itself.
Props
import type {
UploadProps,
UploadTriggerProps,
UploadDropzoneProps,
UploadDropzoneTriggerProps,
UploadStatusProps,
UploadFileListProps,
UploadFileItemProps,
UploadToastsProps,
UsePasteFilesOptions,
} from "@dimah-s3/ui";Upload
Prop
Type
UploadTrigger
Prop
Type
UploadDropzone
Prop
Type
UploadDropzoneTrigger
Prop
Type
UploadStatus
Prop
Type
UploadFileList
Prop
Type
UploadFileItem
Prop
Type
UploadToasts
Prop
Type
usePasteFiles
Prop
Type
Frequently asked questions
Call event.preventDefault() in the trigger's onClick:
<UploadTrigger
onClick={(event) => {
if (!signedIn) event.preventDefault();
}}
/>Pass disabled to UploadTrigger to disable only that trigger while the
hook stays enabled. While the hook is disabled, the picker stays closed
whatever the prop says:
<UploadTrigger disabled={!canEdit} />Use the dismiss action on a settled card, or call upload.reset().
UploadTrigger is the button, and UploadStatus is the status it used to
render under it. Put them together, as in Button.