Dropzone
A file drop target on react-dropzone, after diragb/shadcn-dropzone: 128px minimum, 1px dashed border-strong on surface, an Upload glyph over a 13px label and a 12px caption stating every limit — accept, maxSize, maxFiles — which the target points aria-describedby at. Hover surface-2; a drag that will be accepted turns the edge solid primary on primary-soft, one that will be rejected takes the warning pair, one the browser cannot judge yet takes the neutral step. Accepted files render as stacked Attachment chips with a remove action and, when fileStatus reports a failure, a retry; a rejection is a warning Marker beneath naming the file, the reason and the next step. The cap holds across drops and duplicates are dropped. Every react-dropzone option is a prop; children is an optional render function over its state.
<Dropzone accept={{ "image/*": [] }} maxSize={1024 * 1024} maxFiles={3} onDrop={(accepted) => …} />
<Dropzone label="Attach a file" showFilesList={false}>{(state) => (state.isDragAccept ? "Drop it" : "Attach a file")}</Dropzone>
<Dropzone fileStatus={(file) => (failed.has(file.name) ? "error" : "idle")} onRetry={(file) => …} />Default
Any file, any size. Drop or click, then remove a chip; the list is the component's own.
Images, 1 MB, three at most
The caption states all three limits and names the target's description. Drag a PDF over it to see the reject state; drop one and the Marker says which rule it broke. The cap counts every drop, so a fourth file is refused and a repeat of one already listed is dropped.
0 accepted through onDrop
Failure and retry
fileStatus reports a file that did not upload: the chip takes the warning pair and gains a Retry beside Remove, because a failed upload is recoverable.
Custom content
A render function replaces the glyph, label and caption; the list is off so the consumer can show files its own way. label still supplies the accessible name, so it matches what is on screen, and the 2 MB limit is still described — read out, not drawn, since the layout is the consumer's.
Disabled
Pointer events stay on so the reason can be read; cursor not-allowed, half opacity, aria-disabled on the target, which keeps its tab stop and states the reason in the caption and its title.