A modern, accessible, TypeScript-first React file upload library.
Drag-and-drop, image previews, progress tracking — with zero backend opinions.
📖 Documentation · ✦ Live Demo
- TypeScript-first — full types out of the box, no
@types/package needed - Accessible by default — ARIA roles, keyboard navigation, screen reader support
- ESM + CJS — works with Next.js, Vite, Remix, and any modern bundler
- Zero backend opinions — plug in your own upload logic (S3, Cloudinary, custom API)
- Image previews built-in — thumbnails for images, file icons for everything else
- Per-file progress tracking — animated progress bar per file with status updates
- Headless or styled — use the ready-made UI or bring your own with
useFileUpload - Manual or auto upload — choose whether files upload immediately or wait for a button click
- Fully customisable — style any part of the component with className props
- Tiny bundle — zero dependencies beyond React
npm install react-dropkit
# or
yarn add react-dropkit
# or
pnpm add react-dropkitPeer dependencies: react >= 17, react-dom >= 17
import { Dropzone } from 'react-dropkit'
export default function App() {
return (
<Dropzone
validation={{ accept: ['image/*'], maxSize: 5 * 1024 * 1024 }}
onFilesAdded={(files) => console.log('Added:', files)}
/>
)
}import { Dropzone } from 'react-dropkit'
<Dropzone /><Dropzone
validation={{
accept: ['image/*', '.pdf'], // images and PDFs only
maxSize: 10 * 1024 * 1024, // 10MB max
minSize: 1024, // 1KB min
maxFiles: 5, // max 5 files
}}
multiple={true}
/>Files upload automatically the moment they are added:
<Dropzone
onUpload={async (file) => {
const formData = new FormData()
formData.append('file', file.file)
await fetch('/api/upload', { method: 'POST', body: formData })
}}
/>Files sit in idle state until the user clicks Upload:
<Dropzone
autoUpload={false}
showUploadButton
showClearButton
uploadButtonLabel="Send files"
clearButtonLabel="Remove all"
onUpload={async (file) => {
const formData = new FormData()
formData.append('file', file.file)
await fetch('/api/upload', { method: 'POST', body: formData })
}}
/>Style the inner drop zone and file list independently:
<Dropzone
className="my-wrapper"
dropzoneClassName="my-drop-area"
fileListClassName="my-file-list"
/><Dropzone multiple={false} />Pass children to replace the default UI while keeping all the logic:
<Dropzone>
<div style={{ padding: 40, textAlign: 'center' }}>
<p>📂 Drop your files here</p>
</div>
</Dropzone>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 formData = new FormData()
formData.append('name', name)
if (files[0]) formData.append('avatar', files[0].file)
await fetch('/api/profile', { method: 'POST', body: formData })
}
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</button>
</form>
)
}For full control over the UI, use the hook directly:
import { useFileUpload, FileList } from 'react-dropkit'
export function MyUploader() {
const {
files,
isDragActive,
isDragReject,
getRootProps,
getInputProps,
inputRef,
removeFile,
uploadAll,
clearFiles,
} = useFileUpload({
multiple: true,
validation: {
accept: ['image/*'],
maxSize: 5 * 1024 * 1024,
},
onUpload: async (file) => {
// your upload logic
},
onError: (message) => {
console.error(message)
},
})
return (
<div>
<div
{...getRootProps()}
style={{
border: `2px dashed ${isDragReject ? 'red' : isDragActive ? 'blue' : 'gray'}`,
padding: 32,
borderRadius: 8,
cursor: 'pointer',
}}
>
<input {...getInputProps()} ref={inputRef} />
<p>{isDragActive ? 'Drop here!' : 'Drag files or click to browse'}</p>
</div>
<FileList files={files} onRemove={removeFile} />
{files.length > 0 && (
<div>
<button onClick={uploadAll}>Upload All</button>
<button onClick={clearFiles}>Clear</button>
</div>
)}
</div>
)
}| Prop | Type | Default | Description |
|---|---|---|---|
validation |
ValidationOptions |
{} |
File type, size, and count rules |
multiple |
boolean |
true |
Allow multiple files |
disabled |
boolean |
false |
Disable the drop zone |
autoUpload |
boolean |
true |
Upload files immediately on add. Set to false for manual upload |
showUploadButton |
boolean |
false |
Show a built-in Upload All button |
showClearButton |
boolean |
false |
Show a built-in Clear All button |
uploadButtonLabel |
string |
"Upload All" |
Custom label for the upload button |
clearButtonLabel |
string |
"Clear All" |
Custom label for the clear button |
onFilesAdded |
(files: UploadedFile[]) => void |
— | Called when new files pass validation |
onFilesChange |
(files: UploadedFile[]) => void |
— | Called on every change — add, remove, clear |
onFileRemoved |
(id: string) => void |
— | Called when a file is removed |
onUpload |
(file: UploadedFile) => Promise<void> |
— | Your upload handler per file |
onError |
(error: string) => void |
— | Called on validation failure or upload error |
className |
string |
"" |
Class on the outer wrapper |
dropzoneClassName |
string |
"" |
Class on the inner drop zone area |
fileListClassName |
string |
"" |
Class on the file list |
children |
ReactNode |
— | Override the default drop zone UI |
| Prop | Type | Description |
|---|---|---|
files |
UploadedFile[] |
Array of files to display |
onRemove |
(id: string) => void |
Called when remove is clicked |
className |
string |
Custom class |
| Prop | Type | Description |
|---|---|---|
file |
UploadedFile |
Single file to display |
onRemove |
(id: string) => void |
Called when remove is clicked |
className |
string |
Custom class |
| Prop | Type | Description |
|---|---|---|
progress |
number |
0–100 |
status |
FileStatus |
idle, uploading, success, error |
className |
string |
Custom class |
| Option | Type | Default | Description |
|---|---|---|---|
validation |
ValidationOptions |
{} |
File validation rules |
multiple |
boolean |
true |
Allow multiple files |
autoUpload |
boolean |
true |
Auto upload on file add |
onUpload |
(file: UploadedFile) => Promise<void> |
— | Upload handler |
onFilesAdded |
(files: UploadedFile[]) => void |
— | Called when files are added |
onFilesChange |
(files: UploadedFile[]) => void |
— | Called on every state change |
onFileRemoved |
(id: string) => void |
— | Called when a file is removed |
onError |
(error: string) => void |
— | Called on error |
| 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 drop zone container |
getInputProps() |
InputHTMLAttributes |
Spread onto a hidden input element |
inputRef |
RefObject<HTMLInputElement> |
Attach to input so clicking opens file browser |
addFiles(files) |
(File[]) => void |
Programmatically add files |
removeFile(id) |
(string) => void |
Remove a file by ID |
clearFiles() |
() => void |
Remove all files |
uploadFile(id) |
(string) => Promise<void> |
Upload a single file by ID |
uploadAll() |
() => Promise<void> |
Upload all idle or failed files |
openFileDialog() |
() => void |
Programmatically open the file browser |
interface ValidationOptions {
accept?: string[] // ['image/*', '.pdf', 'video/mp4']
maxSize?: number // bytes — e.g. 5 * 1024 * 1024 for 5MB
minSize?: number // bytes
maxFiles?: number // max total files allowed
}Accept supports:
- Wildcard MIME types:
image/*,video/* - Exact MIME types:
image/png,application/pdf - File extensions:
.pdf,.docx,.csv
type FileStatus = 'idle' | 'uploading' | 'success' | 'error'
interface UploadedFile {
id: string
file: File
preview?: string // blob URL for images, undefined for others
progress: number // 0–100
status: FileStatus
error?: string
}import {
formatFileSize, // formatFileSize(1024) → "1 KB"
isFileAccepted, // checks MIME type or extension
isFileSizeValid, // checks against max/min size
isImageFile, // true if file.type starts with "image/"
getFileExtension, // getFileExtension("doc.pdf") → "PDF"
createPreview, // creates blob URL for images
revokePreview, // cleans up blob URLs
} from 'react-dropkit'react-dropkit is fully compatible with Next.js App Router and Pages Router.
For App Router, mark your component as a client component:
'use client'
import { Dropzone } from 'react-dropkit'- Added
autoUploadprop — toggle between auto and manual upload mode - Added
showUploadButtonandshowClearButtonprops — built-in action buttons on<Dropzone /> - Added
uploadButtonLabelandclearButtonLabel— custom button text - Added
dropzoneClassName— style the inner drop zone area independently - Added
fileListClassName— style the file list independently
- Initial release
Contributions are welcome! Please open an issue first to discuss what you'd like to change.
git clone https://github.com/abideen-program/react-dropkit
cd react-dropkit
npm install
npm test
npm run devMIT © Abideen · Documentation · npm · GitHub