2.0 beta — new releases can include breaking changes. Stable 1.x docs
dimah-s3v2.0.0-beta.0
ComponentsUpload

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.

PartRenders
UploadTriggerA Button that opens the file picker
UploadDropzoneA drop area. By default, a trigger with the route's limits and the status
UploadDropzoneTriggerThe part of a dropzone you click, or focus and press Enter, to pick files
UploadStatusOne card for one file, or a summary above the file list for several
UploadFileListEvery file in the queue, as rows or a grid. A function child renders each
UploadFileItemThe card for one file, with its progress, error, and cancel or retry
UploadToastsProgress, 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.

Drop your contract, or browsePDF only. Up to 20.0 MB.

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

On this page