Drawer

Opens a focused task from the bottom edge. Native dialog with platform focus handling and backdrop.

Notes

A bottom panel. Escape closes it.

Up

Notes

A bottom panel. Escape closes it.

Use it for

  • Phone-first panels that rise from the bottom edge: options, a share sheet, a short form.
  • Content that reads in one screen.

Not for

  • Desktop side panels; use Sheet.
  • Long content; the drawer is not a page.

Three ways in. They share one source, so the pixels match whichever you pick.

1. Import the package

npm install @noorddev/vlak-react

// once, next to your app's root
import "@noorddev/vlak-react/css";

import { Button, Drawer, DrawerBody, DrawerTitle } from "@noorddev/vlak-react";

Precompiled React and one stylesheet. No compiler to configure. Per-component imports work too: @noorddev/vlak-react/components/drawer.

2. Vendor the source

npx @noorddev/vlak-cli add drawer

The StyleX leaf lands in components/vlak/ with its dependencies, for your own compiler to own. See StyleX.

3. Through shadcn

npx shadcn add https://vlak.dev/r/drawer.json

The same registry item, installed by shadcn's CLI.

CSS only

<link rel="stylesheet" href="node_modules/@noorddev/vlak/css/vlak.css" />

No React. Link vlak.css and use the markup and classes below.

import { useState } from "react";
import { Button, Drawer, DrawerBody, DrawerTitle } from "@noorddev/vlak-react";

const [open, setOpen] = useState(false);

<Button variant="ghost" onClick={() => setOpen(true)}>Notes</Button>
<Drawer open={open} onClose={() => setOpen(false)} closeLabel="Close">
  <DrawerTitle>Notes</DrawerTitle>
  <DrawerBody>A bottom panel. Escape closes it.</DrawerBody>
</Drawer>

Drawer

A native <dialog> from the bottom edge. The platform provides the focus trap, Escape, and the backdrop; the title names it.

PropTypeDefaultDescription
openrequiredboolean
onClose() => void
dismissablebooleanEscape (and any other platform close request) asks the parent to close. Off, the dialog must be answered; maps to closedby="none".
lightDismissbooleanA click on the backdrop closes too; maps to closedby="any".
closeLabelstringRenders a labelled close button in the top corner.

Also accepts Omit<DialogHTMLAttributes<HTMLDialogElement>, "open" | "onClose">.

DrawerBody

Also accepts HTMLAttributes<HTMLParagraphElement>.

DrawerTitle

PropTypeDefaultDescription
as"h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "span" | "div""h2"

Also accepts HTMLAttributes<HTMLHeadingElement>.

KeysDoes
EscapeCalls onClose (when dismissable, the default)
Tab, Shift + TabCycles focus inside the drawer
<dialog class="rs-drawer" aria-labelledby="notes-title" open><button class="rs-drawer-close" type="button" aria-label="Close">&times;</button><h2 class="rs-drawer-title" id="notes-title">Notes</h2><p class="rs-drawer-body">A bottom panel. Escape closes it.</p></dialog>
.rs-drawer.rs-drawer-title.rs-drawer-body.rs-drawer-close