VI Controls

Star on GitHubFeedback
VI ControlsNative HTMLot-drop

Dropzone

Files, dropped or chosen. The area is only its corner marks, and files carried over it close them in; the sentence is a real file input, and what you hold is listed as attachments.

Report an issue with Dropzone

Example

  • Halden brief, second round.pdf 2.4 MB
  • Stamp sizes.png 3.6 MB
Rest
Hover
Focus
Files over it
ghost, holding two
ghost, files over it
Disabled

Install

Run it in a project that has the shadcn CLI set up. It brings the 0nlyType base and attachment along.

npx shadcn@latest add https://thedirectors.agency/ui/r/dropzone.json

Usage

dropzone-example.tsx
"use client"import * as React from "react"import { Dropzone } from "@/components/ui/dropzone"// ponytail: stand-in files of the right size, made once, so the docs can show what's held.const file = (name: string, bytes: number, type: string) => new File([new Uint8Array(bytes)], name, { type, lastModified: 0 })const useHeld = () => React.useMemo(() => [file("Halden brief, second round.pdf", 2_400_000, "application/pdf"), file("Stamp sizes.png", 3_600_000, "image/png")], [])export default function Example() {return (<div className="grid w-full max-w-[40rem] gap-(--ot-space-8)"><Dropzone accept=".pdf,.png,.svg" maxSize={10e6} name="marks" hint="PDF, PNG or SVG, up to 10 MB each." /><Dropzone variant="ghost" prompt="Drop the second round here" hint="Anything you've marked up. It stays until you send it." defaultFiles={useHeld()} /></div>)}

Contract

  • Underneath: a native file input, plus a hook.
  • Anatomy: <div class="ot-drop" data-variant="corners | ghost"> holding <label class="ot-drop-zone ot-corners"> (the file input as .ot-sr, for ghost .ot-drop-ghost (aria-hidden), .ot-drop-words ending in .ot-link "choose", then an optional .ot-drop-hint that describes the input), then an ot-attachments list of what's held. data-over while files are carried over it, data-disabled.
  • An area defined only by its corner marks (ot-corners, --ot-corner at --ot-space-4), after "the silence that heals": a frame that doesn't close. The sentence is the control: "Drop files here, or choose". The whole area is the input's label, so a click anywhere opens the picker; Tab reaches the real input, and focus draws the highlighter through "choose" (the link's own look) and sets the corners in the accent.
  • Pointing sketches the corners in pencil. Carry files over it and the corners close in by --ot-space-4 and ink, landing with spiccato, and the words ink; let go or leave and they spring back out. The corners' colour is a registered --ot-drop-c, so it crosses over rather than jumps.
  • What's held is listed as attachment rows, each waiting (idle, the hairline ring of a file still to go) with its size and a bracket Remove. Each drop or choice adds to them, and the same file isn't held twice. A refused file is a crimson row that says what to fix: "This takes PDF, PNG or SVG. Choose another file." or "Choose one under 10 MB; this is 14.2 MB." The real input always holds exactly what's listed (through a DataTransfer), so a form sends what you see. Resetting the form clears the files and refused rows, and calls onFilesChange with no files, as removing the last file does. list={false} leaves the listing to you, for rows with upload progress.
  • Ghost: after the huge ghost numerals behind "the silence that heals". The number of files you carry stands huge and faint behind the words (--ot-fff in the expression italic, ink at 8%, 14% while over), set against the area's foot and end and cropped by them, two figures as the poster's 14 and 08. Dropped, it stays as the number held. It turns over as the count moves, up as files come and down as they go.
  • States: rest, hover, focus, files over it, holding, refused (the row), disabled (the corners at the rule, the words in pencil). Forced colours: the corners are GrayText, Highlight on focus, CanvasText while over; the ghost is dropped.
  • Keyboard: Tab to the input; Enter or Space opens the picker (native). Remove is a button.

Props

Props of Dropzone
PropTypeDefaultWhat it does
variant"corners" | "ghost""corners"corners: the corner marks of "the silence that heals" frame the area; pointing sketches them in pencil, files carried over close them in and ink them, focus sets them in the accent. ghost: the same, and the number of files you carry stands huge and faint behind the words, after the poster's ghost numerals; once dropped it stays as the number held, turning over as it changes.
acceptstringThe file input's accept, such as .pdf,.png or image/*. A file that doesn't match is listed in crimson and says what the area takes.
maxSizenumberThe largest file, in bytes. A larger one is refused with its size and the limit.
multiplebooleantrueTake several files. Each drop or choice adds to what's held; the same file isn't held twice.
namestringSubmits the files with a form. The real input always holds exactly what's listed, removals included.
promptstring"Drop files here"The start of the sentence; ", or choose" follows it.
hintReactNodeWhat it takes, in pencil under the sentence. Describes the input.
defaultFilesFile[]Files held from the start, such as a saved draft's.
onFilesChange(files: File[]) => voidCalled with every file held after each drop, choice or removal.
listbooleantrueList what's held as attachment rows, each with Remove. Turn it off to list them yourself, with their upload progress.
disabledbooleanfalseThe corners and words go pencil and nothing is taken.

Craft

Where its move comes from

Where its move comes from
ComponentReferenceMove
Dropzone"the silence that heals" cornersThe area is only its corner marks; files carried over close them in
Ghost dropzone"the silence that heals" ghost numeralsThe number of files carried stands huge and faint behind the words

How it moves

How it moves
ComponentArticulationWhat moves
DropzoneClose inFiles carried over close the corners in and ink them, landing with spiccato
Ghost dropzoneCountThe ghost number turns over as files come and go