A modern, accessible, TypeScript-first React library for file uploads. Drag-and-drop, image previews, progress tracking — with zero backend opinions.
npm install react-dropkit
Built to fill the real gaps in existing file upload libraries — without the bloat.
Full types out of the box. No @types/ package needed. Every prop and hook return is fully typed.
ARIA roles, keyboard navigation, screen reader support, and focus management — baked in from day one.
Works in Next.js App Router, Vite, Remix, and Create React App. Fixes the broken ESM found in react-dropzone.
Images get real thumbnails from the file itself. Other types show clean icons with extension badges. No upload required.
Each file gets its own animated progress bar with live status updates. Ready → Uploading → Uploaded or Failed.
Use <Dropzone /> for instant UI, or useFileUpload() to build a completely custom experience.
Accepts MIME wildcards, exact types, and extensions. Includes the cross-browser Windows/Safari MIME bug fix.
Plug in your own upload logic — S3, Cloudinary, Supabase, your own API. You handle transport, we handle UI.
Import only what you use. If you only import useFileUpload, only that ships to your users.
import { Dropzone } from 'react-dropkit' export default function App() { return ( <Dropzone validation={{ accept: ['image/*', '.pdf'], maxSize: 5 * 1024 * 1024 }} onFilesAdded={(files) => console.log(files)} /> ) }
<Dropzone onUpload={async (file) => { const formData = new FormData() formData.append('file', file.file) await fetch('/api/upload', { method: 'POST', body: formData, }) }} />
import { Dropzone } from 'react-dropkit' export function BasicUpload() { return ( <Dropzone multiple validation={{ accept: ['image/*', '.pdf'], maxSize: 10 * 1024 * 1024, maxFiles: 5, }} onFilesChange={(files) => console.log(files.length)} onError={(err) => console.error(err)} /> ) }
// Files sit idle until the user clicks Upload. // Use autoUpload=false + showUploadButton together. <Dropzone autoUpload={false} showUploadButton showClearButton uploadButtonLabel="Send files" clearButtonLabel="Remove all" validation={{ accept: ['image/*', '.pdf'], maxFiles: 5 }} onUpload={async (file) => { const fd = new FormData() fd.append('file', file.file) await fetch('/api/upload', { method: 'POST', body: fd }) }} onError={(err) => console.error(err)} /> // Custom styling example <Dropzone className="my-wrapper" dropzoneClassName="my-drop-area" fileListClassName="my-file-list" />
import { useFileUpload, FileList } from 'react-dropkit' export function CustomUpload() { const { files, isDragActive, isDragReject, getRootProps, getInputProps, inputRef, removeFile, uploadAll, clearFiles, } = useFileUpload({ multiple: true, validation: { accept: ['image/*'] }, onUpload: async (file) => { /* your logic */ }, }) return ( <div> <div {...getRootProps()} style={{ border: `2px dashed ${isDragReject ? 'red' : 'gray'}` }} > <input {...getInputProps()} ref={inputRef} /> <p>{isDragActive ? 'Drop here!' : 'Click or drag'}</p> </div> <FileList files={files} onRemove={removeFile} /> <button onClick={uploadAll}>Upload All</button> </div> ) }
import { useState } from 'react' import { useFileUpload } from 'react-dropkit' export function ProfileForm() { const [name, setName] = useState('') const { files, getRootProps, getInputProps, inputRef } = useFileUpload({ multiple: false, validation: { accept: ['image/*'] }, }) const handleSubmit = async (e) => { e.preventDefault() const fd = new FormData() fd.append('name', name) if (files[0]) fd.append('avatar', files[0].file) await fetch('/api/profile', { method: 'POST', body: fd }) } return ( <form onSubmit={handleSubmit}> <input value={name} onChange={e => setName(e.target.value)} /> <div {...getRootProps()}> <input {...getInputProps()} ref={inputRef} /> <p>Drop avatar here</p> </div> <button type="submit">Save Profile</button> </form> ) }
import { useFileUpload } from 'react-dropkit' export function AvatarUpload() { const { files, getRootProps, getInputProps, inputRef } = useFileUpload({ multiple: false, validation: { accept: ['image/*'], maxSize: 2 * 1024 * 1024 }, }) return ( <div {...getRootProps()} style={{ width: 96, height: 96, borderRadius: '50%', cursor: 'pointer' }} > <input {...getInputProps()} ref={inputRef} /> {files[0]?.preview ? <img src={files[0].preview} style={{ borderRadius: '50%' }} /> : <span>Upload photo</span> } </div> ) }
<Dropzone onUpload={(file) => new Promise((resolve, reject) => { const xhr = new XMLHttpRequest() const fd = new FormData() fd.append('file', file.file) xhr.upload.onprogress = (e) => { if (e.lengthComputable) { // real progress from the server const pct = Math.round((e.loaded / e.total) * 100) } } xhr.onload = () => xhr.status === 200 ? resolve() : reject(new Error('Failed')) xhr.onerror = () => reject(new Error('Network error')) xhr.open('POST', '/api/upload') xhr.send(fd) })} />
<Dropzone /> props| Prop | Type | Description |
|---|---|---|
| validation | ValidationOptions | File type, size, and count rules. |
| multiple | boolean | Allow multiple files. Default: true. |
| disabled | boolean | Disables the drop zone entirely. Default: false. |
| autoUpload | boolean | Upload files immediately on add. Set to false for manual upload. Default: true. |
| showUploadButton | boolean | Renders a built-in Upload All button below the file list. Default: false. |
| showClearButton | boolean | Renders a built-in Clear All button below the file list. Default: false. |
| uploadButtonLabel | string | Custom label for the upload button. Default: "Upload All". |
| clearButtonLabel | string | Custom label for the clear button. Default: "Clear All". |
| onFilesAdded | (files: UploadedFile[]) => void | Fires when new files pass validation. |
| onFilesChange | (files: UploadedFile[]) => void | Fires on every change. Always reflects the current files array. |
| onFileRemoved | (id: string) => void | Fires when a file is removed. |
| onUpload | (file: UploadedFile) => Promise<void> | Your upload handler. Library manages status around your Promise. |
| onError | (error: string) => void | Fires on validation failure or upload error. |
| className | string | Custom class on the outer wrapper. |
| dropzoneClassName | string | Custom class on the inner drop zone area. |
| fileListClassName | string | Custom class on the file list. |
| children | ReactNode | Override the default UI while keeping all logic. |
ValidationOptions| Option | Type | Description |
|---|---|---|
| accept | string[] | Wildcards (image/*), exact MIME types (image/png), extensions (.pdf). Mix freely. |
| maxSize | number | Max file size in bytes. e.g. 5 * 1024 * 1024 for 5MB. |
| minSize | number | Min file size in bytes. |
| maxFiles | number | Max total files allowed. |
useFileUpload() returns| Value | Type | Description |
|---|---|---|
| files | UploadedFile[] | Current files with status, progress, preview. |
| isDragActive | boolean | True when dragging over the drop zone. |
| isDragReject | boolean | True when dragged files don't match accepted types. |
| getRootProps() | HTMLAttributes | Spread onto your container. Handles drag events, click, keyboard. |
| getInputProps() | InputHTMLAttributes | Spread onto a hidden input for file browser support. |
| inputRef | RefObject<HTMLInputElement> | Attach to input so clicking opens the file browser. |
| addFiles(files) | (File[]) => void | Programmatically add files. |
| removeFile(id) | (string) => void | Remove a file by ID. |
| clearFiles() | () => void | Remove all files and revoke preview URLs. |
| uploadAll() | () => Promise<void> | Upload all idle or failed files in parallel. |
| openFileDialog() | () => void | Programmatically open the native file browser. |
Against the libraries developers reach for today.
| Feature | react-dropzone | react-dropkit ✦ | FilePond |
|---|---|---|---|
| Built-in UI | ✕ | ✓ | ✓ |
| Image previews | ✕ Manual | ✓ Auto | ✓ |
| Progress bars | ✕ Manual | ✓ Built-in | ✓ |
| Headless hook | ✓ | ✓ | ✕ |
| TypeScript-first | ⚠ @types/ needed | ✓ Native | ⚠ Afterthought |
| ESM + Next.js | ⚠ Broken ESM | ✓ Full ESM + CJS | ✓ |
| Zero dependencies | ✓ | ✓ | ✕ |
| Accessibility | ⚠ Partial | ✓ WCAG | ⚠ Partial |