npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-upload-pro

v0.2.0

Published

Drop-in React file upload & dropzone component — drag & drop, progress, previews, chunked uploads, image compression, S3 / Cloudinary / Firebase / Supabase adapters, 21+ UI variants, i18n, TypeScript. A batteries-included react-dropzone alternative.

Readme

react-upload-pro

Drop-in React file upload component — drag & drop, progress bars, previews, retries, image compression, and direct-to-cloud uploads.

Drag & drop · chunk uploads · cloud adapters · 21+ UI variants · i18n in 23 locales · a11y · SSR-safe · TypeScript-first.

npm live demo types license bundle

▶ Open the live demo & playground

Try every variant and option in the browser, then copy the generated code.

Live demo · Quick start · API reference · Changelog · Report a bug


Table of contents


Quick start (60 seconds, no backend needed)

npm install react-upload-pro
import { Dropzone } from "react-upload-pro";
import { createMockAdapter } from "react-upload-pro/cloud";
import "react-upload-pro/styles.css";

// Fake uploads with real progress — swap for your server later.
const mock = createMockAdapter();

export default function App() {
  return (
    <Dropzone
      cloud={mock}
      mode="auto" // start uploading as soon as files are dropped
      accept="image/*,application/pdf"
      maxSize={10 * 1024 * 1024} // 10 MB
      previewable
      onUploadSuccess={(file) => console.log("uploaded", file.url)}
    />
  );
}

Drop a few files: you get thumbnails, progress bars, speed, ETA, retry and remove buttons, and a fullscreen preview. Nothing leaves the browser.

Prefer clicking around first? The live playground lets you change every option and copy the matching code.


Connect your server

Replace cloud={mock} with endpoint. Each file is sent as a multipart/form-data POST with the file in a field named file:

<Dropzone
  endpoint="/api/upload"
  mode="auto"
  onUploadSuccess={(file) => console.log(file.url)} // read from your JSON response
/>

Any backend works. A minimal Express server:

// server.js — npm install express multer
import express from "express";
import multer from "multer";

const upload = multer({ dest: "uploads/" });
const app = express();

app.use("/uploads", express.static("uploads"));
app.post("/api/upload", upload.single("file"), (req, res) => {
  // Return a URL and it shows up as `file.url` on the client.
  res.json({ url: `/uploads/${req.file.filename}` });
});

app.listen(3001);

Running Vite on another port? Proxy the API in vite.config.ts: server: { proxy: { "/api": "http://localhost:3001" } }.

file.url is read from url, secure_url, location, fileUrl or file_url in the JSON response (also when nested under data), or from a plain-text response that is itself a URL. The full response is always available as file.response.

Next.js route handler, S3 presigned URLs, and other backends are covered in Step-by-step setup and Cloud adapters.


Why react-upload-pro?

react-dropzone stops at "drop files, get a callback". react-upload-pro ships everything you need to ship a real upload feature:

| | react-dropzone | react-upload-pro | | ---------------------------------------------------- | ----------------- | --------------------------- | | Drag & drop, click to browse, hook API | ✅ | ✅ | | Type / size / count validation | ✅ | ✅ + magic-number checks | | Uploads to your server with progress, speed, ETA | ❌ bring your own | ✅ | | Retries, pause / resume / cancel, chunked uploads | ❌ | ✅ | | Ready-made UI: file list, previews, progress bars | ❌ | ✅ 21+ variants | | Direct to S3 / Cloudinary / Firebase / Supabase / … | ❌ | ✅ | | Client-side image compression | ❌ | ✅ | | Built-in translations | ❌ | ✅ 23 locales |

Everything in the box:

  • ✅ Inputs — drag/drop, click, paste from clipboard, folder upload (recursive)
  • ✅ Upload engine — instant / manual / auto / queue modes, parallel + sequential, chunked, pause / resume / retry / cancel
  • ✅ Cloud adapters — AWS S3 (presigned), Cloudinary, Firebase Storage, Supabase, DigitalOcean Spaces, Azure Blob, GCS, plus a mock adapter for demos and tests
  • ✅ Image compression — resize and re-encode JPEG / PNG / WebP in the browser before upload
  • ✅ Validation — MIME, extension, size (min/max), max files, duplicates, magic-number signatures, custom async validators
  • ✅ Preview — image / video / audio / PDF / text / Office (icon) with zoom & rotate
  • ✅ Progress — bar / circle / striped, per-file + aggregate, EWMA speed + ETA
  • ✅ 21+ UI variants across Minimal / Business / Creative / Enterprise / Layouts
  • ✅ a11y — ARIA roles, keyboard nav, focus rings, RTL
  • ✅ i18n — 23 built-in locales (en, hi, gu, fr, de, ar, zh, es, pt, ru, ja, ko, it, tr, id, bn, ur, nl, pl, vi, th, he, fa) + custom messages
  • ✅ Theming — light / dark / auto with CSS variables and a Tailwind preset
  • ✅ Framework agnostic — works with Axios / Fetch / GraphQL / REST via a custom upload function
  • ✅ Tree-shakeable — core ships without framer-motion or any cloud SDK

Migrating from react-dropzone

useDropzone has the same shape — getRootProps, getInputProps, isDragActive, open, and the same accept object format — so most components move over by changing the import:

// Before
import { useDropzone } from "react-dropzone";

// After — same hook, plus uploads, progress and retries
import { useDropzone } from "react-upload-pro";

function Uploader() {
  const { getRootProps, getInputProps, isDragActive, files, start } = useDropzone({
    accept: { "image/*": [".png", ".jpg"] },
    maxSize: 5 * 1024 * 1024,
    endpoint: "/api/upload", // new: where files go
  });

  return (
    <div>
      <div {...getRootProps()}>
        <input {...getInputProps()} />
        {isDragActive ? "Drop here…" : "Drag files here, or click to select"}
      </div>
      <button onClick={() => start()}>Upload {files.length} files</button>
    </div>
  );
}

Differences to know about:

  • onDrop receives (accepted: UploadFile[], rejected: ValidationError[]). Each UploadFile wraps the native File as .file and adds status, progress, url, and more.
  • The hook keeps the list of files for you (files, remove, removeAll), so you don't need your own useState.
  • Uploads start when you call start(), or automatically with mode: "auto".

Install

# npm
npm install react-upload-pro

# yarn
yarn add react-upload-pro

# pnpm
pnpm add react-upload-pro

# bun
bun add react-upload-pro

Peer deps: react >= 17, react-dom >= 17. framer-motion is optional (only some animated variants need it).


Import the styles

You must import the bundled stylesheet exactly once — otherwise the dropzone renders unstyled (no dashed border, no accent color, no layout).

// Vite, CRA, Remix, Astro — anywhere with a global entry file
import "react-upload-pro/styles.css";
// Next.js App Router
// app/layout.tsx
import "react-upload-pro/styles.css";
// Next.js Pages Router
// pages/_app.tsx
import "react-upload-pro/styles.css";

The stylesheet is self-contained — it ships every utility class the components use, so you do not need Tailwind, PostCSS, or any extra build step in your app to get the demo look. If you also use Tailwind in your project, see Tailwind setup below to share design tokens.


Full sample with comments

A complete, copy-paste-ready example. Every prop is annotated so you can see exactly what each piece does — delete the bits you don't need.

// src/components/MyUploader.tsx

// 1️⃣ Core component + a couple of types we'll use in the callbacks.
import {
  Dropzone,
  ThemeProvider,
  I18nProvider,
  type UploadFile,
  type ValidationError,
} from "react-upload-pro";

// 2️⃣ Bundled stylesheet. Import it once at the top level of your app
//    (e.g. main.tsx / layout.tsx) — including it here works too.
import "react-upload-pro/styles.css";

import { useState } from "react";

export default function MyUploader() {
  // Track files that the dropzone rejects (wrong type, too big, etc.) so we
  // can show them in a toast or modal. Optional — leave it out if you don't
  // need rejection UI.
  const [rejected, setRejected] = useState<ValidationError[]>([]);

  return (
    // ThemeProvider gives you light / dark / auto. Wrap once near the root.
    <ThemeProvider defaultTheme="auto">
      {/* I18nProvider translates every internal string. 23 locales built in. */}
      <I18nProvider locale="en">
        <Dropzone
          // ───── Where the files go ─────
          // Pass `endpoint` for a standard multipart POST, OR pass `cloud`
          // (S3 / Cloudinary / Firebase / …) for direct-to-bucket uploads.
          endpoint="/api/upload"

          // ───── Validation ─────
          accept="image/*,application/pdf"     // MIME globs or extensions
          maxSize={10 * 1024 * 1024}           // 10 MB per file
          minSize={1024}                       // 1 KB per file
          maxFiles={5}                         // total file cap
          multiple                             // allow picking many at once
          rejectDuplicates                     // skip identical files
          compressImages                       // shrink big photos before upload

          // ───── Upload behaviour ─────
          mode="auto"          // 'manual' | 'instant' | 'auto' | 'queue'
          strategy="parallel"  // 'parallel' | 'sequential'
          concurrency={3}      // parallel upload slots
          retries={2}          // retry attempts per file on network errors
          chunkSize={5 * 1024 * 1024}  // 5 MB chunks (omit for single-shot)

          // ───── Built-in UI extras ─────
          previewable          // eye icon → fullscreen preview
          editable             // pencil icon → rename + tag
          clipboard            // allow ⌘V / Ctrl+V paste
          scrollAfter={5}      // gallery scrolls after 5 files
          maxHeight="320px"    // height of the scroll region

          // ───── Theming ─────
          // `accent` drives borders, focus rings, progress fill, primary
          // buttons, and hover states. Accepts hex (`'#10b981'`), an RGB
          // triplet (`'16 185 129'`), or any CSS color. Scoped to THIS
          // dropzone — multiple instances on one page can have different
          // accents without leaking globally.
          accent="#10b981"
          accentFg="#ffffff"   // optional — auto-derived from accent when omitted

          // ───── Copy ─────
          label="Drop your files here"
          hint="PNG, JPG, or PDF — up to 10 MB each, max 5 files"

          // ───── Events ─────
          onDrop={(accepted, rejected) =>
            console.log("dropped", { accepted, rejected })
          }
          onDropRejected={setRejected} // collect validation errors for UI
          onUploadStart={(file: UploadFile) =>
            console.log("uploading", file.name)
          }
          onUploadProgress={(file) =>
            console.log(`${file.name}: ${Math.round(file.progress * 100)}%`)
          }
          onUploadSuccess={(file) =>
            console.log("✅ done →", file.url)
          }
          onUploadError={(file, err) =>
            console.error("❌", file.name, err.message)
          }
        />

        {/* Toast/banner for rejected files. Replace with your own UI. */}
        {rejected.length > 0 && (
          <ul style={{ color: "crimson", marginTop: 12 }}>
            {rejected.map((e, i) => (
              <li key={i}>
                {e.file?.name ?? "file"} — {e.message}
              </li>
            ))}
          </ul>
        )}
      </I18nProvider>
    </ThemeProvider>
  );
}

What this gives you out of the box:

  • 🖱️ Drag-drop, click-to-browse, paste from clipboard
  • 🧪 Live validation with friendly error messages
  • 📊 Per-file progress bars + speed + ETA
  • 🔁 Automatic retries with exponential backoff
  • 👁️ Fullscreen preview for images, PDFs, video, audio
  • ✏️ Inline rename + metadata edit modal
  • 🗜️ Big photos resized in the browser before they upload
  • 🌗 Light / dark / auto themes with a single prop
  • 🌐 23-locale i18n with RTL support

Step-by-step setup

1. Vite + React

# 1. Create a Vite app
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install

# 2. Install the package
npm install react-upload-pro
// src/App.tsx
import { Dropzone } from "react-upload-pro";
import "react-upload-pro/styles.css";

export default function App() {
  return (
    <div style={{ padding: 24 }}>
      <Dropzone endpoint="/api/upload" maxSize={5 * 1024 * 1024} />
    </div>
  );
}
npm run dev   # → http://localhost:5173

Dropped files wait in the list until the user clicks Upload (mode="manual", the default). Add mode="auto" to upload as soon as files are dropped. Don't have a server yet? Use createMockAdapter().

2. Next.js (App Router)

npx create-next-app@latest my-app --typescript --app
cd my-app
npm install react-upload-pro
// app/upload/page.tsx
"use client";

import { Dropzone, ThemeProvider } from "react-upload-pro";
import "react-upload-pro/styles.css";

export default function UploadPage() {
  return (
    <ThemeProvider defaultTheme="auto">
      <Dropzone
        endpoint="/api/upload"
        accept="image/*"
        maxSize={10 * 1024 * 1024}
        mode="auto"
      />
    </ThemeProvider>
  );
}
// app/api/upload/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
  const formData = await req.formData();
  const file = formData.get("file") as File;
  // ...save to disk / S3 / Cloudinary / etc.
  return NextResponse.json({ url: "/uploads/" + file.name });
}

Why 'use client'? The component uses browser APIs (drag events, File, FileReader). The package emits a "use client" banner, so importing from a server component works — but pages that render it directly need the directive.

3. Create React App

npx create-react-app my-app --template typescript
cd my-app
npm install react-upload-pro

Same src/App.tsx as the Vite example — CRA just works.


Tailwind setup (optional)

You only need this section if your own app uses Tailwind and you want to re-use react-upload-pro's design tokens (text-rup-accent, bg-rup-bg, etc.) in your own components.

Not using Tailwind? Stop here — import "react-upload-pro/styles.css" is already enough. The bundled stylesheet ships every utility the components need.

Plug in the preset:

// tailwind.config.js
module.exports = {
  presets: [require("react-upload-pro/tailwind")],
  content: [
    "./src/**/*.{ts,tsx,js,jsx}",
    "./node_modules/react-upload-pro/dist/**/*.{js,cjs}",
  ],
};

Then anywhere in your app:

<button className="bg-rup-accent text-rup-accent-fg">Custom button</button>

The accent color follows the same --rup-accent variable the dropzone uses, so picking a new accent updates your custom UI too.


Core usage

Minimum

<Dropzone endpoint="/api/upload" />

Files are listed with an Upload button underneath. Pass mode="auto" to skip the button and upload right away.

Real-world example

import { Dropzone, type UploadFile } from "react-upload-pro";
import "react-upload-pro/styles.css";

export function ProfilePictureUploader() {
  return (
    <Dropzone
      endpoint="/api/avatar"
      accept="image/*"
      maxSize={2 * 1024 * 1024} // 2 MB
      maxFiles={1}
      multiple={false}
      mode="instant" // upload immediately on drop
      compressImages={{ maxWidth: 512, maxHeight: 512 }}
      previewable // eye icon → fullscreen preview
      editable // pencil icon → rename + tag
      retries={2}
      onUploadStart={(f: UploadFile) => console.log("uploading", f.name)}
      onUploadSuccess={(f) => console.log("done", f.url)}
      onUploadError={(f, e) => alert(e.message)}
    />
  );
}

Custom label / hint

<Dropzone
  endpoint="/api/upload"
  label="Drop your resume here"
  hint="PDF or DOCX, up to 5 MB"
/>

Take a photo on mobile

// Opens the back camera on phones; desktop browsers show the normal file picker.
<Dropzone endpoint="/api/upload" accept="image/*" capture="environment" />

Use capture="user" for the front camera (selfies, avatars).

Manual control with the render prop

<Dropzone endpoint="/api/upload" maxSize={5e6}>
  {({ getRootProps, getInputProps, files, start, isUploading }) => (
    <div>
      <div
        {...getRootProps()}
        className="border-2 border-dashed p-8 rounded-lg"
      >
        <input {...getInputProps()} />
        Drop files or click
      </div>
      <p>{files.length} queued</p>
      <button onClick={() => start()} disabled={isUploading}>
        Upload
      </button>
    </div>
  )}
</Dropzone>

Pre-built variants

21+ designs grouped into 5 categories. Every variant accepts the same options as Dropzone, so any feature works on any look. See them all in the live playground →

import {
  MinimalGlass,
  BusinessCRM,
  EnterpriseDocs,
  LayoutModal,
} from "react-upload-pro/variants";

<MinimalGlass endpoint="/api/upload" mode="auto" accent="#6366f1" />;

| Category | Variants | | -------------- | ---------------------------------------------------------------------------------------- | | Minimal | MinimalModern, MinimalGlass, MinimalNeumorphic, MinimalMaterial, MinimalInline | | Business | BusinessCRM, BusinessDashboard, BusinessSaaS | | Creative | CreativeGradient, CreativeAnimated, CreativePremium, CreativeAvatar | | Enterprise | EnterpriseDocs, EnterpriseTeam, EnterpriseMediaLibrary, EnterpriseFullscreen | | Layouts | LayoutBox, LayoutCard, LayoutSidebar, LayoutModal, LayoutFloating |

In manual mode (the default) each variant shows Remove all / Upload buttons under its file list; hideActions turns them off. CreativeAvatar uploads as soon as a picture is picked.


Hooks-first usage

For full control — no built-in UI, no gallery, just upload state and helpers — use useDropzone:

import { useDropzone, UploadGallery } from "react-upload-pro";
import "react-upload-pro/styles.css";

function MyUploader() {
  const {
    getRootProps,
    getInputProps,
    files,
    isUploading,
    isDragActive,
    start,
    pause,
    resume,
    remove,
    retry,
    removeAll,
  } = useDropzone({
    endpoint: "/api/upload",
    accept: { "image/*": [".png", ".jpg", ".webp"] },
    maxSize: 5 * 1024 * 1024,
    mode: "manual", // wait for explicit start()
    chunkSize: 5 * 1024 * 1024, // 5 MB chunks
    strategy: "parallel",
    concurrency: 3,
  });

  return (
    <div>
      <div
        {...getRootProps()}
        className={`border-2 border-dashed p-8 rounded-lg ${
          isDragActive ? "border-blue-500 bg-blue-50" : "border-gray-300"
        }`}
      >
        <input {...getInputProps()} />
        {isDragActive ? "Drop here…" : "Drop files or click"}
      </div>

      <UploadGallery
        files={files}
        onRemove={(f) => remove(f.id)}
        onRetry={(f) => retry(f.id)}
        onPause={(f) => pause(f.id)}
        onResume={(f) => resume(f.id)}
      />

      <div style={{ display: "flex", gap: 8 }}>
        <button onClick={() => start()} disabled={isUploading}>
          Upload
        </button>
        <button onClick={removeAll}>Clear</button>
      </div>
    </div>
  );
}

pause, resume, retry, cancel and remove take a file id. Want the stock buttons instead of your own? Render <UploadActions files={files} onUpload={start} onRemoveAll={removeAll} />.


Image compression

Phone photos are often 3–10 MB. Turn on compressImages and JPEG / PNG / WebP files are resized and re-encoded in the browser before they are validated and uploaded — so a 6 MB photo can still pass maxSize={2 * 1024 * 1024}.

// Defaults: longest side 1920px, quality 0.8, same format
<Dropzone endpoint="/api/upload" compressImages />

// Or tune it
<Dropzone
  endpoint="/api/upload"
  compressImages={{ maxWidth: 1280, maxHeight: 1280, quality: 0.7, mimeType: "image/webp" }}
/>

GIF, SVG, and non-image files pass through untouched, and an image is never replaced by a bigger one. The same logic is available on its own:

import { compressImage } from "react-upload-pro";

const smaller = await compressImage(file, { maxWidth: 800 });

Use inside a form

Give the dropzone a name and every uploaded file is submitted with the surrounding <form> as a hidden field holding its URL — no useState, no onChange wiring. It works with plain HTML forms, Next.js Server Actions, and Remix actions.

// app/profile/actions.ts
"use server";

export async function saveProfile(formData: FormData) {
  const name = formData.get("displayName");
  const photos = formData.getAll("photos"); // → ['https://…/a.jpg', 'https://…/b.jpg']
  // …save to your database
}
// app/profile/page.tsx
"use client";

import { Dropzone } from "react-upload-pro";
import "react-upload-pro/styles.css";
import { saveProfile } from "./actions";

export default function ProfilePage() {
  return (
    <form action={saveProfile}>
      <input name="displayName" />
      <Dropzone name="photos" endpoint="/api/upload" mode="auto" accept="image/*" />
      <button type="submit">Save</button>
    </form>
  );
}
  • Only finished uploads are submitted. Use mode="auto" so files upload as soon as they're added.
  • Submit something other than the URL with formValue, e.g. formValue={(file) => (file.response as { id: string }).id}.
  • Every variant accepts name too. With useDropzone, render <UploadFormValue name="photos" files={files} /> inside your form.

Custom upload function (fetch / axios)

Need your own request logic — axios interceptors, GraphQL, a signed URL from your API? Pass any object with an upload function as cloud. Report progress with onProgress and honour signal so cancel / pause work:

import axios from "axios";
import { Dropzone, type CloudAdapter } from "react-upload-pro";

const axiosUpload: CloudAdapter = {
  name: "axios",
  async upload(file, { onProgress, signal }) {
    const body = new FormData();
    body.append("file", file.file, file.name);
    const res = await axios.post("/api/upload", body, {
      signal,
      onUploadProgress: (e) => onProgress(e.loaded, e.total ?? file.size),
    });
    return { url: res.data.url }; // becomes file.url
  },
};

<Dropzone cloud={axiosUpload} mode="auto" />;

To have a failure retried (retries), throw { code: "network", message: "…", retryable: true }.


Cloud adapters

Upload directly to your cloud bucket without proxying through your server. Credentials never leave your backend — the adapter just consumes presigned URLs / signed tokens / SAS.

AWS S3

import { useDropzone } from "react-upload-pro";
import { createS3Adapter } from "react-upload-pro/cloud";

const s3 = createS3Adapter({
  getPresignedUrl: async (file) => {
    const res = await fetch("/api/s3/presign", {
      method: "POST",
      body: JSON.stringify({ name: file.name, type: file.type }),
    });
    return res.json(); // { url, method: 'PUT', headers? }
  },
});

useDropzone({ cloud: s3, mode: "auto" });
// /api/s3/presign (Next.js Route Handler example)
import { S3Client } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { PutObjectCommand } from "@aws-sdk/client-s3";

const s3 = new S3Client({ region: process.env.AWS_REGION });

export async function POST(req: Request) {
  const { name, type } = await req.json();
  const cmd = new PutObjectCommand({
    Bucket: process.env.S3_BUCKET,
    Key: name,
    ContentType: type,
  });
  const url = await getSignedUrl(s3, cmd, { expiresIn: 60 });
  return Response.json({ url, method: "PUT" });
}

Other adapters

import {
  createCloudinaryAdapter,
  createFirebaseStorageAdapter,
  createSupabaseAdapter,
  createDigitalOceanAdapter,
  createAzureBlobAdapter,
  createGcsAdapter,
  createMockAdapter, // fake uploads for demos, Storybook and tests
} from "react-upload-pro/cloud";

Every adapter has the same shape — pass it to cloud: on Dropzone or useDropzone. See docs/cloud.md for each adapter's options.

createMockAdapter({ durationMs, failRate }) simulates an upload with real progress, cancel and retry — handy for trying the UI, Storybook stories, and tests.


Validation

useDropzone({
  accept: { "image/*": [".png", ".jpg"] },
  minSize: 1024, // 1 KB
  maxSize: 5 * 1024 * 1024, // 5 MB
  maxFiles: 10,
  maxTotalSize: 50 * 1024 * 1024, // 50 MB across all files
  imageDimensions: { minWidth: 400, minHeight: 400 }, // pixels
  rejectDuplicates: true, // same name + size + lastModified
  validators: [
    async (file) =>
      file.name.includes(" ")
        ? { code: "custom", message: "No spaces in filename" }
        : null,
  ],
});

| Rule | Rejection code | Notes | | ----------------- | -------------------------------------- | --------------------------------------------------------------------- | | accept | file-invalid-type | MIME globs, extensions, or { "image/*": [".png"] } | | minSize / maxSize | file-too-small / file-too-large | Bytes, per file | | maxFiles | too-many-files | Counts files already in the list | | maxTotalSize | total-size-exceeded | Bytes, all files combined — including ones added earlier | | imageDimensions | image-too-small / image-too-large | minWidth, minHeight, maxWidth, maxHeight in pixels. Checked after compressImages; SVG and non-images are skipped | | rejectDuplicates| duplicate-file | | | validators | whatever you return | Sync or async; run after the built-in rules |

Every rejection comes with a readable message, e.g. "Image is 200×150px; it must be at least 400×400px." getImageSize(file) is exported too if you need the pixel size yourself.

For security-sensitive flows, validate the real magic number instead of trusting the MIME the browser reports:

import { detectSignature } from "react-upload-pro";

const actual = await detectSignature(file); // → 'image/png' | 'application/pdf' | ...
if (!actual) throw new Error("unknown file type");

Showing rejection errors as a modal

import {
  Dropzone,
  ValidationErrorsModal,
  type ValidationError,
} from "react-upload-pro";
import { useState } from "react";

function App() {
  const [errors, setErrors] = useState<ValidationError[]>([]);
  return (
    <>
      <Dropzone endpoint="/api/upload" onDropRejected={setErrors} />
      <ValidationErrorsModal
        open={errors.length > 0}
        errors={errors}
        onClose={() => setErrors([])}
      />
    </>
  );
}

Internationalization (i18n)

23 built-in locales — wrap with I18nProvider and the dropzone UI translates automatically:

import { I18nProvider, Dropzone } from "react-upload-pro";

<I18nProvider locale="ja">
  <Dropzone endpoint="/api/upload" />
</I18nProvider>;

Supported locales

| Code | Language | RTL | | ---- | ---------------- | --- | | en | English | | | es | Español | | | fr | Français | | | de | Deutsch | | | it | Italiano | | | pt | Português | | | nl | Nederlands | | | pl | Polski | | | ru | Русский | | | tr | Türkçe | | | zh | 中文 | | | ja | 日本語 | | | ko | 한국어 | | | vi | Tiếng Việt | | | th | ไทย | | | id | Bahasa Indonesia | | | hi | हिन्दी | | | gu | ગુજરાતી | | | bn | বাংলা | | | ar | العربية | ✓ | | ur | اردو | ✓ | | he | עברית | ✓ | | fa | فارسی | ✓ |

Custom messages

Override any string per-locale:

<I18nProvider
  locale="en"
  messages={{ dropHere: "Drop your resume here", browse: "Pick a PDF" }}
>
  <Dropzone endpoint="/api/upload" />
</I18nProvider>

Check the RTL set programmatically

import { rtlLocales } from "react-upload-pro";

const isRtl = rtlLocales.has(currentLocale);

Theming

import { ThemeProvider, useTheme } from "react-upload-pro";

// defaultTheme: 'light' | 'dark' | 'auto'
<ThemeProvider defaultTheme="auto">
  <Dropzone endpoint="/api/upload" />
</ThemeProvider>;

Custom accent color

{/* Hex string — most common */}
<Dropzone endpoint="/api/upload" accent="#10b981" />

{/* RGB triplet (the format CSS variables use internally) */}
<Dropzone endpoint="/api/upload" accent="16 185 129" />

{/* Any CSS color the browser understands */}
<Dropzone endpoint="/api/upload" accent="rebeccapurple" />

{/* With a custom foreground (button text on accent background).
    Auto-derived from luminance when omitted. */}
<Dropzone endpoint="/api/upload" accent="#facc15" accentFg="#000000" />

Each accent is scoped to that dropzone instance — the value is applied as an inline --rup-accent CSS variable on the component's outer wrapper, so multiple dropzones on the same page can each have their own accent without leaking globally.

Need the accent to apply page-wide (e.g. share it with your own buttons)? Set it as a CSS variable on any ancestor:

:root {
  --rup-accent: 16 185 129; /* RGB triplet, no rgb() wrapper */
  --rup-accent-fg: 255 255 255;
}

Every variant also accepts the same prop:

import { MinimalGlass, BusinessCRM } from "react-upload-pro/variants";

<MinimalGlass accent="#6366f1" endpoint="/api/upload" />
<BusinessCRM accent="#10b981" endpoint="/api/upload" />

The accent drives borders, buttons, progress fill, focus rings, hover states, and scrollbars.


Upload modes & strategies

| Option | Values | Default | Description | | ---------------- | -------------------------------------------------------------- | ------------ | ------------------------- | | mode | 'manual' | 'instant' | 'auto' | 'queue' | 'manual' | When to start uploading | | strategy | 'parallel' | 'sequential' | 'parallel' | How to dispatch the queue | | concurrency | number | 3 | Parallel upload slots | | retries | number | 2 | Retry attempts per file | | retryBackoffMs | number | 500 | Doubles each retry | | chunkSize | number (bytes) | unset | Single-shot if unset |

Mode cheat sheet

  • manual — files queue up; the user clicks the built-in Upload button (or you call start())
  • instant — each file starts uploading the moment it's dropped
  • auto — like instant, but waits 100 ms so several quick drops or pastes start as one batch
  • queue — starts automatically and uploads strictly one at a time, in drop order, ignoring concurrency

Props reference

<Dropzone> (most common)

| Prop | Type | Notes | | ------------------ | -------------------------------------------- | ------------------------------------------------------------------ | | endpoint | string | URL for the multipart POST. Use cloud: for direct-to-S3 etc. | | cloud | CloudAdapter | Direct cloud upload or a custom upload function | | accept | string | Accept | "image/*", ".pdf,.docx", or { 'image/*': ['.png'] } | | maxSize | number | Bytes | | minSize | number | Bytes | | maxFiles | number | | | maxTotalSize | number | Bytes, all files combined. See Validation | | imageDimensions | { minWidth?, minHeight?, maxWidth?, maxHeight? } | Pixels. See Validation | | multiple | boolean | Default true | | directory | boolean | Folder upload (recursive) | | clipboard | boolean | Paste from clipboard. Default true | | capture | boolean | 'user' | 'environment' | Open the phone camera. See Take a photo on mobile | | rejectDuplicates | boolean | Default false | | compressImages | boolean | ImageCompressionOptions | Resize JPEG / PNG / WebP before upload. See Image compression | | disabled | boolean | | | mode | 'manual' \| 'instant' \| 'auto' \| 'queue' | Default 'manual' | | strategy | 'parallel' \| 'sequential' | Default 'parallel' | | concurrency | number | Default 3 | | retries | number | Default 2 | | chunkSize | number | Bytes. Unset = single-shot upload | | label | ReactNode | Main line of text in the drop area | | hint | ReactNode | Small descriptive line under the label | | hideActions | boolean | Hide the Upload / Remove all buttons shown in manual mode | | hideGallery | boolean | Hide the built-in file list | | name | string | Submit uploaded file URLs with the surrounding <form>. See Use inside a form | | formValue | (file) => string \| undefined | What to submit per file when name is set. Default file.url | | galleryLayout | 'list' \| 'grid' \| 'table' \| 'compact' | Default 'list' | | previewable | boolean | Eye icon → fullscreen preview | | editable | boolean | Pencil icon → rename + tag + describe | | scrollAfter | number | List becomes scrollable above this count | | maxHeight | string | CSS height of the scrollable region | | width / height | string | Outer container CSS sizing | | accent | string | Hex / RGB triplet / CSS color. Scoped to this instance. | | accentFg | string | Foreground on accent surfaces. Auto-derived from accent. | | onDrop | (accepted, rejected) => void | | | onDropRejected | (errors) => void | | | onUploadStart | (file) => void | | | onUploadProgress | (file) => void | file.progress is 0..1; also file.speed, file.eta | | onUploadSuccess | (file) => void | file.url and file.response are set | | onUploadError | (file, error) => void | | | onAllComplete | (files) => void | Every file finished (success, error, or cancelled) |

Full API surface

  • Components — Dropzone, UploadArea, UploadActions, UploadFormValue, UploadButton, UploadProgress, UploadPreview, UploadGallery, UploadModal, FilePreviewModal, FileEditModal, ValidationErrorsModal
  • Hooks — useDropzone, useUploader, useUploadQueue, useUploadProgress, useFilePreview
  • Providers — ThemeProvider, I18nProvider
  • Core — UploadQueue, validateFile, validateBatch, matchesAccept
  • Cloud (subpath) — createS3Adapter, createCloudinaryAdapter, createFirebaseStorageAdapter, createSupabaseAdapter, createDigitalOceanAdapter, createAzureBlobAdapter, createGcsAdapter, createMockAdapter
  • Variants (subpath) — 21 named exports (see Pre-built variants)
  • Utilities — compressImage, getImageSize, formatBytes, formatSpeed, formatEta, formatPercent, getFileCategory, detectSignature, wrapFile, generatePreview, revokePreview, cn
  • i18n exports — translations, rtlLocales, type Locale, type Translations

See docs/API.md for the full reference.


TypeScript

Fully typed — every public API has .d.ts. The package exports both ESM and CJS with separate type declarations so it works in any modern bundler.

import type {
  DropzoneOptions,
  DropzoneState,
  UploadFile,
  UploadStatus,
  ValidationError,
  CloudAdapter,
  ImageCompressionOptions,
  Locale,
  Theme,
} from "react-upload-pro";

SSR / Next.js notes

  • Every public surface is SSR-safe — DOM APIs are only touched inside useEffect.
  • The package emits a "use client" directive, so importing from a server component works transparently. Pages that render Dropzone directly still need 'use client' at the top.
  • The base CSS (react-upload-pro/styles.css) is plain CSS — import it once in your root layout.
// app/layout.tsx
import "react-upload-pro/styles.css";

Performance

  • Default render path is O(n) on file count.
  • For galleries beyond a few hundred files, use scrollAfter={N} to cap the rendered region. For thousands of files, virtualize with your own list library — the gallery layout is intentionally pluggable.
  • previewUrl is lazily generated and automatically revoked on remove / unmount, so no object-URL leaks.
  • Core is tree-shakeable. framer-motion and cloud SDKs are not imported until you opt in.

Contributing / development

git clone https://github.com/yogeshgabani/react-upload-pro.git
cd react-upload-pro
npm install

# 🎮 Interactive playground — every variant, every option, live code preview
npm run dev          # → http://localhost:5173

# Other scripts
npm run dev:lib      # tsup --watch — rebuild library on every save
npm run storybook    # individual component stories on :6006
npm test             # vitest unit + component
npm run test:e2e     # playwright smoke
npm run build        # tsup → dist/ (ESM + CJS + .d.ts)
npm run typecheck    # tsc --noEmit

The playground (npm run dev, or the hosted version at react-upload-pro.netlify.app) is the fastest way to explore the API — every prop is wired to a UI control and the generated code updates live as you tweak.

PRs welcome — please:

  1. Open an issue first for non-trivial changes
  2. Add a test for new behavior
  3. Run npm run typecheck && npm test before pushing

If this library saves you time, a ⭐ on GitHub helps other developers find it.


Changelog

Every release is documented in CHANGELOG.md — features added, bugs fixed, breaking changes called out. The live playground also has a "Version history" panel under the variant gallery so you can review the release timeline right from the demo.


License

MIT © 2026 Yogesh Gabani