Drawer
A sheet from below, lifted by the fermata's arc. Drag the arc down to put it away.
Report an issue with DrawerExample
Open
thirds, at two thirds
solid, at rest
solid, pulled halfway
solid, set solid
Install
Run it in a project that has the shadcn CLI set up. It brings the 0nlyType base, dialog, and fraction along.
npx shadcn@latest add https:// thedirectors.agency/ ui/ r/drawer.jsonUsage
This example also uses Button and Checkbox.
npx shadcn@latest add https:// thedirectors.agency/ ui/ r/ button.json https:// thedirectors.agency/ ui/ r/ checkbox.jsonimport type { CSSProperties, ReactNode } from "react"import { Button } from "@/components/ui/button"import { Checkbox, CheckboxGroup } from "@/components/ui/checkbox"import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerTitle, DrawerTrigger } from "@/components/ui/drawer"import { Fraction } from "@/components/ui/fraction"const notes = ["Keep the flag. The blue is older than the company and every islander knows it.","Lose the anchor. It says harbour; the line is about the crossing.","The timetable is the real product. Set it large enough to read from the quay.","One typeface, two weights. The ferries' names in the italic.","Paint the hulls last. The livery follows the type, not the other way round.",]function Filters() {return (<CheckboxGroup legend="Kinds of work"><Checkbox>Identity</Checkbox><Checkbox>Web</Checkbox><Checkbox>Motion</Checkbox></CheckboxGroup>)}function Notes() {return (<ol className="grid gap-4" style={{ maxWidth: "36rem" }}>{notes.map((n) => (<li key={n}>{n}</li>))}</ol>)}export default function Example() {return (<div className="flex flex-wrap items-center gap-10"><Drawer><DrawerTrigger asChild><Button variant="bracket">Filter the work</Button></DrawerTrigger><DrawerContent><DrawerTitle>Filter the work</DrawerTitle><Filters /><DrawerClose asChild><Button variant="statement" data-autofocus>Show 7 projects</Button></DrawerClose></DrawerContent></Drawer><Drawer><DrawerTrigger asChild><Button variant="bracket">Read the notes</Button></DrawerTrigger><DrawerContent variant="thirds" handleLabel="Height of the notes"><DrawerTitle>Notes on the Halden brief</DrawerTitle><DrawerDescription>Drag the arc up for more of them, or press the arrow keys on it.</DrawerDescription><Notes /><DrawerClose asChild><Button variant="bracket">Close</Button></DrawerClose></DrawerContent></Drawer><Drawer><DrawerTrigger asChild><Button variant="bracket">Sort the work</Button></DrawerTrigger><DrawerContent variant="solid"><DrawerTitle>Sort the work</DrawerTitle><Filters /><DrawerClose asChild><Button variant="statement" data-autofocus>Sort by year</Button></DrawerClose></DrawerContent></Drawer></div>)}/** A drawer drawn still, in the page, for the states row. */function Still({ variant, style, share, children }: { variant?: string; style?: CSSProperties; share?: number; children: ReactNode }) {return (<dialogopenclassName="ot-drawer"data-variant={variant}style={{ position: "relative", inset: "auto", translate: "none", width: "min(22rem, 100%)", maxHeight: "none", height: "auto", boxShadow: "none", transition: "none", ...style }}><span className="ot-drawer-handle" aria-hidden="true" style={{ width: "fit-content" }}><svg viewBox="0 0 40 22"><path d="M3 20 A17 17 0 0 1 37 20" /><circle cx="20" cy="17" r="2.6" /></svg></span>{share && (<span className="ot-drawer-share" aria-hidden="true"><Fraction count={share} total={3} /></span>)}<div className="ot-drawer-body">{children}</div></dialog>)}Contract
- Underneath: native
<dialog>, through the dialog item. - Anatomy:
<dialog class="ot-sheet" closedby="any">with.ot-sheet-spine(the title again, aria-hidden).<dialog class="ot-drawer" closedby="any">with<button class="ot-drawer-handle">. Both share the dialog backdrop. - A sheet is a page slid in from the end edge, its title running up the spine like a book's (
ot-spine), in its own column: the sheet's start padding, on the left, or the right in right-to-left. A drawer rises from below and lands with spiccato; its handle is the fermata's arc. Dragging the handle down sets--pullanddata-pulling, and past a threshold it closes. - Keyboard: Escape closes; the handle closes on Enter. Smooth scrolling pauses while open.
- Sheet variants go on
data-variant(SheetContent variant): spine (the default), shelf, rag, fold. - Shelf, after books standing on a shelf: a sheet opened from inside a shelf sheet stands in front of it, one spine column narrower, so the spine behind stays in view, set back to pencil, and is the way back: pointing at it (the bare backdrop) or Escape puts the front sheet away and focus returns to the control that opened it. Only the front sheet blurs the page. Below 40rem the column is space-7 and the spine mf.
- Rag, after "Healthy habits" cut from its paper and "Less is more." set flush right: the words stand flush to the window's edge and rag toward the page, and the paper is cut to that rag, a step for every line, with a hairline along the cut. The script reads the line boxes (after the fonts), draws
<svg class="ot-sheet-rag" aria-hidden="true">behind them and setsdata-laid; until then it is plain paper with its edge. The box hangs from the top only as tall as its words, and a click on its bare part is a click outside. Start and end sides only. It re-cuts on resize and when<html>changes its face. Forced colours: Canvas paper, CanvasText cut. - Fold, after the index entry that opens to its page and "Less is more." set between its hairlines: the row that opened it unfolds into the page. Before it opens (and again before it closes) the sheet writes the opener's box to
--fold-t/r/b/l; the page is clipped to that band and opens out to the window, the row's two hairlines (ink,::beforeand::after) riding the clip's edges to the top and the foot, while the row's name (itsrows-title, or the trigger's own words) grows into the title, set at--ot-ff(--ot-fbelow 40rem), by a transform measured from one to the other so their first lines meet. The rest arrives once there is room. A reversed row opens from its ink (data-from-ink: the page starts ink and washes to paper). Put away, it folds back into the row and the title flies back to the name. The whole window, no spine;sideis ignored. The trigger remembers itself (SheetTrigger), so open it from<Row asChild><SheetTrigger>with one Sheet round the Rows; opened by state alone, a line across the middle parts. Reduced motion: it opens at once. Forced colours: the lines in CanvasText. - Panels (
SheetPanels,.ot-sheet-panelsholdingnav.ot-sheet-spine.ot-sheet-trailandsection.ot-sheet-panelpages): pages inside one sheet, the chapters of the book on its spine, and the scale contrast of "It has to be design.". The spine becomes the trail, read up from the foot (right to left as well): the names you came through small (mp) in the pencil, the one you are in large (f, 200) in ink,aria-current="page". Stepping in (SheetPanelLink to), the name you left shrinks into the trail and the new one writes up the spine; the new page slides in from the sheet's own edge while the old one leaves the other way, the two crossing in one grid cell (hiddenandinerton the pages you are not in, the exit kept bydisplayallow-discrete). The small names are buttons and the way back. Focus goes to the new page's heading, and going back returns it to the link that stepped in; the sheet scrolls to the top. Each opening starts at the root, whose title names the dialog. Below 40rem the trail ismfoverp. It belongs to the sheet, not the drawer: a sheet is already a page with a spine to carry the path, where a drawer is a tray whose height the thirds decide. - Drawer variants go on
data-variant(DrawerContent variant): arc (the default), thirds, solid. - Thirds, after SPECTRA's "1/3": the drawer stops at a third, two thirds or the whole height, and the share stands at the top end corner as a Fraction (aria-hidden), thin and large. The handle is then
role="slider"(vertical, 1 to 3, "2 of 3"): drag it to the nearest third, or lower than half a third to put it away; a tap or Enter raises it a third, round to one; the arrow keys, Home and End step it. Each opening starts at a third. - Solid, after the compositor's leads: pulling the arc first takes the silence out between the drawer's parts (
--solidfrom 0 to 1 closes the gaps and the padding), as type is set solid when the leads come out; only once it is set solid does the drawer itself move, and letting go past that closes it. Its give is the height it loses, measured on pointerdown. Keyboard as arc.
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
Drawer | Dialog props | The same open, defaultOpen, onOpenChange and alert as Dialog. | |
DrawerContent variant | "arc" | "thirds" | "solid" | "arc" | How the drawer answers the hand. arc: drag the arc down past a short pull, or tap it, and it goes. thirds: it opens at a third of the window and stops at a third, two thirds or the whole height; the arc follows your drag both ways and lets go on the nearest third (below half of one, it goes), a tap raises it a third, and the share stands in the far corner as a Fraction, yours in italic. The arc is then a vertical slider: the arrow keys step it, Home and End go to a third and the whole; Escape closes. solid: pulling the arc first takes out the silence between the drawer's parts, so it closes up and sinks under your hand, as a compositor sets type solid; let go once it is solid and it goes, short of that the silence springs back. |
DrawerContent handleLabel | string | "Put this away" (thirds: "Height of the drawer") | The accessible name of the arc. Tapping it, or pressing Enter on it, closes the drawer; in thirds it raises it a third. |
Craft
Where its move comes from
| Component | Reference | Move |
|---|---|---|
| Drawer | The fermata sign | The handle is the arc |
| Thirds drawer | SPECTRA's "1/3" | It stops at thirds; the share is the fraction in the corner |
| Solid drawer | The compositor's leads (set solid) | The pull takes out the silence first, then the drawer |
How it moves
| Component | Articulation | What moves |
|---|---|---|
| Drawer | Spiccato | It lands with a rebound; it follows your drag down |
| Thirds drawer | Spiccato | It follows the hand both ways and lands on a third; the fraction's wheels turn |
| Solid drawer | Legato | The gaps close under the hand, then the drawer follows it down |