UploadKit
SDK@uploadkitdev/react

UploadDropzone

A drag-and-drop zone with multi-file support, per-file progress bars, and auto-dismissing error toasts.

Drop files here or click to browse

Baseline dropzone · parallel multi-file

Basic usage

import { UploadDropzone } from '@uploadkitdev/react';

export default function Page() {
  return (
    <UploadDropzone
      route="imageUploader"
      onUploadComplete={(results) => {
        // results is UploadResult[] — called when ALL files finish
        console.log('Uploaded:', results.map((r) => r.url));
      }}
      onUploadError={(error) => {
        console.error('Upload failed:', error.message);
      }}
    />
  );
}

Props

PropTypeDefaultDescription
routestringRequired. Route name from your file router.
acceptstring[]Accepted MIME types. Supports wildcards: 'image/*'. Client-side UX validation only.
maxSizenumberMaximum file size in bytes. Client-side UX validation only.
maxFilesnumberMaximum number of files per drop or selection. Files beyond this limit are silently ignored.
metadataRecord<string, unknown>JSON-serializable metadata forwarded to onUploadComplete on the server.
onUploadComplete(results: UploadResult[]) => voidCalled after all accepted files have uploaded successfully.
onUploadError(error: Error) => voidCalled when any individual file upload fails.
disabledbooleanfalseDisables the dropzone and file input.
classNamestringAdditional CSS class(es) for the outer wrapper.
appearancePartial<Record<'container' | 'label' | 'icon' | 'fileItem' | 'progressBar' | 'button', string>>Override CSS classes on specific inner elements.

UploadDropzone accepts a ref forwarded to the outer container <div>.

Visual states

StateAppearance
idleDashed border, upload icon, "Drop files here or click to browse" label
dragging-overBorder turns primary color, light primary background tint
uploadingFile list appears below the zone, each file shows a progress bar
successFile row shows a green checkmark
errorFile row shows a red X; error toast appears and auto-dismisses after 5 seconds

Multi-file uploads

Accept up to 5 images at once:

<UploadDropzone
  route="galleryUploader"
  accept={['image/*']}
  maxFiles={5}
  maxSize={10 * 1024 * 1024}
  onUploadComplete={(results) => {
    // results contains all successfully uploaded files
    results.forEach((r) => console.log(r.url));
  }}
/>

Files upload in parallel batches of 3. If a single file fails, onUploadError is called for that file and the others continue uploading. onUploadComplete is only called when all accepted files succeed.

Per-file progress

Each file in the queue shows an inline progress bar. Files upload in parallel (batches of 3) so progress is independent per file.

<UploadDropzone
  route="videoUploader"
  accept={['video/mp4', 'video/webm']}
  maxSize={500 * 1024 * 1024} // 500 MB
  onUploadComplete={(results) => {
    setVideos(results.map((r) => r.url));
  }}
/>

Rejected files

Files that fail client-side validation (wrong type, too large) are shown as dismissable error toasts below the dropzone. They auto-dismiss after 5 seconds and can be closed manually.

<UploadDropzone
  route="pdfUploader"
  accept={['application/pdf']}
  maxSize={5 * 1024 * 1024}
  onUploadError={(error) => {
    // Called for server-side rejections; client-side rejections show inline toasts
    toast.error(error.message);
  }}
/>

Appearance — targeting inner elements

<UploadDropzone
  route="imageUploader"
  className="border-2"
  appearance={{
    container: 'rounded-2xl min-h-[200px]',
    label: 'text-sm',
    icon: 'w-8 h-8',
    fileItem: 'bg-neutral-50',
    progressBar: 'h-1',
  }}
/>

Keyboard accessibility

The dropzone is focusable (tabIndex={0}) and responds to Enter and Space to open the file picker. It has role="button" and aria-label="Drop files here or click to browse". A screen-reader live region announces the number of files uploading and completed.

On this page