Uploads that show their work.
Animated, accessible file uploads for React. 17 motion presets on a real upload engine: byte-level progress, retries, pause, cancel and validation, with zero dependencies.
npm install upload-motion- 0 runtime dependencies
- 17 motion presets
- ≈ 9 kB headless hook
- TypeScript · ESM + CJS · SSR
Expand it to manage each file, or pause the whole batch.
Playground
Tune it live, copy the code
Every control maps to a real prop on the real component. Drop your own files or generate samples in the browser, then copy a snippet that matches what you see.
import { FileUpload, createXhrUploader } from 'upload-motion';
import 'upload-motion/styles.css';
// Your endpoint: receives multipart form data in the "file" field.
const upload = createXhrUploader({ url: '/api/upload' });
export function Uploader() {
return (
<FileUpload
preset="essential"
upload={upload}
maxFiles={10}
maxSize={25 * 1024 * 1024}
onUploadSuccess={(file, response) => console.log('uploaded', file.name, response)}
/>
);
}Presets
17 presets, one engine
From a quiet admin list to a basketball court. Presets only change presentation, so switching is one prop and your upload logic never changes.
Upload files
Drag and drop, or take the shot.
Engine
The motion is optional. The engine isn’t.
This lab uses only the headless useUploader hook and plain markup. Raise the failure rate, limit concurrency and watch the queue: waiting slots, backoff retries, pause and resume.
- Uploading
- 0
- Queued
- 0
- Done
- 0
- Failed
- 0
- Speed
- –
Load a batch of seven files and watch the queue work.
Or drop your own files here.
import { useUploader, createXhrUploader, formatBytes } from 'upload-motion';
export function Queue() {
const { items, totals, dropzone, pause, resume, cancel, retry } = useUploader({
upload: createXhrUploader({ url: '/api/upload' }),
concurrency: 2,
retry: { attempts: 2 }, // 800 ms, then 1.6 s backoff
});
return (
<div {...dropzone.getRootProps({ focusable: true })}>
<input {...dropzone.getInputProps()} />
<progress value={totals.progress} />
{items.map((item) => (
<Row key={item.id} item={item} onRetry={() => retry(item.id)} />
))}
</div>
);
}- Honest by design. No handler, no “uploaded”. Simulated transports are flagged and badged.
- Progress never runs backwards within an attempt, and 100% waits for the server before it says done.
- Retries with backoff only for retryable failures: network, timeouts, 5xx, 408 and 429.
- Bring any backend. Multipart POST, presigned S3/R2/GCS PUTs, or your own handler.
Quick start
Three ways in
Import the stylesheet once. Works with Vite, Next.js (App and Pages Router), Remix and CRA, on React 18.2 or newer.
- Server components ready. The React entry ships with
"use client", andupload-motion/coreis server-safe. - Any backend. Files arrive as multipart form data in the
filefield, or as a raw presigned PUT.
import { FileUpload, createXhrUploader } from 'upload-motion';
import 'upload-motion/styles.css'; // once, anywhere in your app
const upload = createXhrUploader({ url: '/api/upload' });
export function Attachments() {
return (
<FileUpload
preset="wave-bar"
upload={upload}
accept="image/*,.pdf"
maxSize={25 * 1024 * 1024}
onUploadSuccess={(file, response) => console.log(file.name, response)}
/>
);
}Accessibility
Keyboards, screen readers and calm
Motion is the garnish, never the information. Every preset works without a mouse, announces what happens, and settles down when the OS asks for less motion.
| Key | Action |
|---|---|
| Tab | Every action is a labelled button: browse, upload, pause, cancel, retry, remove |
| Enter / Space | Opens the file dialog on focusable drop zones |
| ⌘V / Ctrl+V | Pastes files into a focused drop zone |
| Enter on a token | Hoop Shot takes the perfect shot, no aiming needed |
| ↑ ↓ Home End Esc | Navigate and close the Wave Bar actions menu |
- Live announcements. Additions, completions and cancellations are polite; errors and rejected files are assertive.
- Real progressbars with readable values such as “42%, 3 seconds left”.
- Not colour alone. Failures always carry an icon and a sentence, like “report.pdf is 31 MB; the limit is 25 MB.”
- Reduced motion collapses animations to instant state changes and removes particles; nothing is hidden.
- Tidy resources. Preview object URLs are revoked, requests abort on unmount, and progress renders once per frame.
API
Props at a glance
Every option is typed. The README has the full reference, the composable parts and the headless API.
| Prop | Type | Default | Description |
|---|---|---|---|
preset | "wave-bar" | "card-fan" | "hoop-shot" | … (17) | "essential" | Animation preset, or your own preset object. |
upload | UploadHandler | – | Transport. Without one, files stay "selected" and are never reported as uploaded. |
accept | string | string[] | – | Same syntax as the HTML accept attribute, e.g. "image/*,.pdf". |
maxSize / minSize | number | – | Bytes per file. Messages read "report.pdf is 31 MB; the limit is 25 MB." |
maxFiles / multiple | number / boolean | – / true | Queue limit; multiple={false} replaces the current file. |
validate | (file, { items }) => string | null | – | Custom rule; return a message to reject. |
autoUpload | boolean | true | Start on add. Card Fan and Hoop Shot default to manual start. |
concurrency | number | 3 | Parallel uploads. |
retry | number | { attempts, delay } | 0 | Automatic retries for retryable errors with exponential backoff. |
store | UploadStore | – | Bring your own store so uploads survive unmounts. |
appearance | { theme, accent, radius, density, … } | – | Theme tokens, all mapped to --um-* CSS variables. |
motion | { speed, intensity, particles, reducedMotion } | – | Global motion controls. Reduced motion follows the OS by default. |
labels | Partial<UploadLabels> | – | Override any string for i18n. |
renderFile / zoneContent | (item, helpers) => ReactNode / ReactNode | – | Replace the file card or the drop-zone content. |
onUploadSuccess … | callbacks | – | onFilesAdded, onFilesRejected, onUploadStart, onUploadProgress, onUploadError, onUploadCancel, onQueueComplete… |
Also exported: Uploader, UploadDropzone, UploadFileList, UploadProgress, useUploader, createUploadStore, createXhrUploader and the per-file visualizers.