Drop Zone
A file upload zone with live drag feedback, validation, per-file progress, cancel, retry and paste support.
Overview
The zone reacts while a file is still being dragged across the window. The dashed frame turns accent-coloured and marches, the icon lifts, and the label changes to “Release to upload”. The frame is an SVG rect, so the dashes stay even at any size.
Each file gets a row with a type glyph, a progress bar fed by your own upload function, and the actions that make sense for its state: cancel while uploading, retry after an error or cancel, and remove when done. Type and size limits are checked before anything is uploaded, and rejections are explained in plain words.
You bring the transport. onUpload receives the file, a progress callback and an AbortSignal, so it works with fetch, XHR, presigned S3 URLs, or anything else that can report progress.
When to use
- Collecting input where the control itself should guide people.
When not to use
- Cases a plain native input already serves well.
Installation
pnpm dlx shadcn@latest add https://sundaeui.dev/r/drop-zone.jsonnpx shadcn@latest add https://sundaeui.dev/r/drop-zone.jsonyarn dlx shadcn@latest add https://sundaeui.dev/r/drop-zone.jsonbunx --bun shadcn@latest add https://sundaeui.dev/r/drop-zone.jsonWorks in any React project. Files are written to components/sundae/. Read the source in the stage’s Code view. Pro assets need a personal token. See authenticated installs.
- No dependencies beyond React 18 or later.
- Copy drop-zone.tsx and drop-zone.css into components/sundae/.
- The CSS file is imported by the component, so no extra configuration is needed.
- Import the component and use it as shown below.
Usage
import { DropZone, type UploadFn } from '@/components/sundae/drop-zone'
const upload: UploadFn = async (file, onProgress, signal) => {
const url = await getPresignedUrl(file)
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.upload.onprogress = (e) => onProgress(e.loaded / e.total)
xhr.onload = () => (xhr.status < 300 ? resolve() : reject(new Error('Upload failed')))
xhr.onerror = () => reject(new Error('Network error'))
signal.addEventListener('abort', () => xhr.abort())
xhr.open('PUT', url)
xhr.send(file)
})
}
<DropZone onUpload={upload} accept={['image/*', '.pdf']} maxSize={20 * 1024 * 1024} />Props
| Prop | Type | Default |
|---|---|---|
onUpload*Your upload. Report progress from 0 to 1, and stop when the signal aborts. | (file, onProgress, signal) => Promise<void> | — |
acceptMIME types or extensions, such as image/* or .pdf. | string[] | — |
maxSizeMaximum bytes per file. | number | 25 MB |
maxFilesMaximum number of files. | number | 10 |
labelMain label. | string | 'Drop files here' |
hintReplaces the default hint line. | ReactNode | — |
allowPasteAccept files pasted with ⌘V or Ctrl+V. | boolean | true |
onChangeReceives the current files and their statuses. | (files) => void | — |
Accessibility
- The zone is a real button that opens the native file picker, so keyboard and screen-reader users never need to drag.
- Each row has a labelled progressbar, and the action buttons are labelled with the file name.
- Rejections are announced through role="alert".
Customization
- Theme with --dz-accent, --dz-success, --dz-error and the surface variables.
- Glyph colours by MIME family live in .sd-drop__glyph[data-kind].
FAQ
- Is Drop Zone free?
- Drop Zone is part of Sundae Pro. The live preview is open to everyone; the source installs with a Pro account.
- How do I install Drop Zone?
- Run npx shadcn@latest add https://sundaeui.dev/r/drop-zone.json. The files are written to components/sundae/. It has no dependencies beyond React 18 or later.
- Does Drop Zone support keyboard and screen readers?
- The zone is a real button that opens the native file picker, so keyboard and screen-reader users never need to drag. Each row has a labelled progressbar, and the action buttons are labelled with the file name.
- Does Drop Zone respect reduced motion?
- Yes. With prefers-reduced-motion set, decorative motion is removed and state changes are instant.
More like this
All forms →OTP Field
Forms · Refined- Pro
Onboarding Flow
Forms · Refined Upload Queue
Forms · Refined- Pro
Photo Uploader
Forms · Refined Jelly Switch
Forms · PlayfulDate Picker
Forms · Understated