Customization
Everything about the uploader's appearance and behaviour is a prop, and everything it paints is a CSS variable you can override. This page covers the theme, the copy, the drag effects and the layout options.
Dark mode
The component ships with a light and a dark palette. darkMode picks one,
"light" (the default) or "dark":
<DropUpload darkMode="dark" />It never reads the visitor's OS preference on its own. That's deliberate: an app that is one theme keeps the uploader on it, and an app with a theme of its own stays the single source of truth. Hand your resolved theme down through the prop and the uploader follows it:
import { useTheme } from "next-themes"
function Uploader() {
const { resolvedTheme } = useTheme()
return <DropUpload darkMode={resolvedTheme === "dark" ? "dark" : "light"} />
}If you want the uploader to follow the OS directly, resolve
prefers-color-scheme in your app (for example with
window.matchMedia("(prefers-color-scheme: dark)")) and pass the result in.
Doing it once at the app level keeps every uploader on the page in sync.
The theme is applied as a data-theme attribute on the component's root, so
every colour token below is scoped to it. Switching the prop re-themes the
uploader immediately, with no remount and no loss of the attached files.
Colours passed explicitly (dragBorderColor, spotlightColor,
dropSmokeColor) win in both themes, so an accent you set stays put when the
theme flips.
Instruction label
Change the call-to-action text shown in the drop area:
<DropUpload instructionLabel="Drag your CV here" />Auto-hide drop area
Hide the drop area once at least one file is attached:
<DropUpload autoHideDropArea />Paste from the clipboard
A screenshot you just took, or a file copied in the file manager, attaches with
Ctrl/Cmd+V anywhere on the page, with no need to click the drop area first. It's on by default:
<DropUpload pasteToUpload={false} />Pasted files go through the same input the drop area writes to, so
allowedFormats, the size limits, maxFiles, the magic-byte check and every
callback (onFilesDropped, onFilesChanged, ...) behave exactly as they do on
a drop.
Text pastes are untouched: the component only steps in when the clipboard
actually carries files, and a paste over an <input>, a <textarea> or a
contenteditable element is always left alone, so an image pasted into a comment
box next to the uploader won't also get attached.
Several uploaders on one page
A paste is a page-wide event with no target of its own, so the uploaders agree on which one takes it. The file is attached once, to:
- the uploader that currently holds the focus, if any does; otherwise
- the first uploader in document order.
So a page with two uploaders never attaches the same screenshot twice, and the
one that takes it is predictable. If you want a specific uploader to be the
only candidate, set pasteToUpload={false} on the others.
Stacked file list
By default every attached file is listed one below the other. Set
fileListMode="stacked" to collapse them into a pile instead: only the most
recently attached file is shown, the rest sit behind it as cards, and a +N
chip expands the pile into the full list. Clicking again re-stacks it.
<DropUpload fileListMode="stacked" />Useful when the uploader is embedded in a form and a long file list would push the rest of the layout around. The pile only appears from the second file onwards. With a single file attached both modes look the same.
The expand and collapse animations honour the visitor's
prefers-reduced-motion setting: when it's on, the pile still expands, just
without the transition.
Spotlight effect
Toggle the hover spotlight effect:
<DropUpload spotlight={false} />Smoke on drop
When a file lands on the drop area a puff of smoke bursts from the exact point it hit, then drifts up and fades. It's on by default and turned off with a single prop:
<DropUpload dropSmoke={false} />The cloud takes its colour from the theme (grey on light, pale on dark) and its size from the area it lands in, so the avatar circle gets a puff and a full-width drop area gets a billow. Any CSS colour overrides it:
<DropUpload dropSmokeColor="#c084fc" />Visitors who asked their OS to reduce motion never see the effect, whatever
dropSmoke is set to.
Border style
The drop area is drawn with a dotted border. borderSolid swaps it for a solid
one:
<DropUpload borderSolid />Drag effect
While files are held over the drop area it plays one of two reactions.
dragEffect picks which:
"pulse"(the default) breathes an inset glow and turns the edge solid."motion-lines"draws the edge as repeating diagonal lines and drifts them along the border until the files land or leave.
<DropUpload dragEffect="motion-lines" />The pointer alone does not start either one: both wait for files. Visitors who asked their OS to reduce motion get the same lines, standing still.
Drag colors
While files are dragged over the drop area two things light up: an inner border
glow and the spotlight that follows the cursor. They default to #3b82f6 and
#60a5fa respectively, and can be changed independently with any CSS color:
<DropUpload dragBorderColor="#a855f7" spotlightColor="#c084fc" />dragBorderColor drives the pulsing glow inside the box; spotlightColor
drives the blurred circle that follows the pointer. Setting spotlight={false}
disables the spotlight entirely, so spotlightColor has no effect then.
dragBorderColor doubles as the component's accent: the per-file upload
progress bar is filled with it, so the drag edge and the bar stay the same
color. To part them, override the CSS variable on the component's root:
.my-uploader {
--du-progress-fill: #f5b547;
}Discard delay
Files are discarded via a long-press. Control the delay (in ms) with
discardFileAwaitTime (default 700):
<DropUpload discardFileAwaitTime={300} />Pre-attached files
initialAttachedFileList seeds the list with files that already exist on your
server, like an edit form that has to show what was uploaded last time. Each entry
needs at least a name, a size (in bytes) and a type; status: "done" and
a url mark it as already stored, and preview gives the thumbnail:
<DropUpload
initialAttachedFileList={[
{
name: "contract.pdf",
size: 248_000,
type: "application/pdf",
status: "done",
url: "https://cdn.example.com/contract.pdf",
},
]}
/>These files are never re-uploaded and never validated, since they're already
yours, but they can be discarded like any other, which fires onDiscardFile
with the entry so you can delete it on your side.
It's an initial value, read once when the component mounts: changing the
array later doesn't re-seed the list. If the list has to change with your data,
remount the component with a key.
Blob URLs you pass in as preview are yours to release. The component only
revokes the ones it created itself.
Width
The uploader is fluid: it fills whatever container you drop it in. Cap it by
overriding --du-max-width on the root:
.my-uploader {
--du-max-width: 350px;
}<div className="my-uploader">
<DropUpload />
</div>The drop area, the file list and the stacked pile all read the same variable, so they stay aligned at any width.
Theming with CSS variables
Every colour and measure the component draws with is a --du-* custom property
declared on its root, and the dark palette is the same set redeclared under
[data-theme="dark"]. Override any of them from your own stylesheet to match
the uploader to your design system. No props needed, and no need to fork the
CSS:
.my-uploader {
--du-bg: #fbf9f5;
--du-fg: #1c1917;
--du-border: #d6cfc4;
--du-progress-fill: #f5b547;
}The ones you're most likely to want:
| Variable | What it paints |
|---|---|
--du-bg | Drop area background |
--du-fg | Primary text and icons |
--du-fg-muted | Secondary copy (formats, size hints) |
--du-border | Drop area border, at rest |
--du-item-bg / --du-item-border | Each row in the file list |
--du-progress-fill / --du-progress-track | Upload progress bar |
--du-success / --du-error | Finished and failed states |
--du-discard-bg / --du-discard-border | Discard button |
--du-drag-border / --du-spotlight / --du-smoke | Drag accents, same values as the props above |
--du-max-width | Cap on the component's width |
--du-avatar-size | Diameter of the avatar circle (set by avatarSize) |
Because the props write these same variables inline on the root, a prop always
beats a stylesheet rule. Set the accent through dragBorderColor or
through --du-drag-border, not both.
Debug mode
debugMode reveals the native file input and extra logging, useful during
development, not intended for production:
<DropUpload debugMode />It's off by default and there's no reason to ship it on: the exposed input is unstyled and the console gets noisy on every drag event.