Drawer
Opens a focused task from the bottom edge. Native dialog with platform focus handling and backdrop.
Preview
In action
When to use
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.
Install
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.
React
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>Props
Drawer
A native <dialog> from the bottom edge. The platform provides the focus trap, Escape, and the backdrop; the title names it.
| Prop | Type | Default | Description |
|---|---|---|---|
openrequired | boolean | ||
onClose | () => void | ||
dismissable | boolean | Escape (and any other platform close request) asks the parent to close. Off, the dialog must be answered; maps to closedby="none". | |
lightDismiss | boolean | A click on the backdrop closes too; maps to closedby="any". | |
closeLabel | string | Renders a labelled close button in the top corner. |
Also accepts Omit<DialogHTMLAttributes<HTMLDialogElement>, "open" | "onClose">.
DrawerBody
Also accepts HTMLAttributes<HTMLParagraphElement>.
DrawerTitle
| Prop | Type | Default | Description |
|---|---|---|---|
as | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "span" | "div" | "h2" |
Also accepts HTMLAttributes<HTMLHeadingElement>.
Keyboard
| Keys | Does |
|---|---|
| Escape | Calls onClose (when dismissable, the default) |
| Tab, Shift + Tab | Cycles focus inside the drawer |
Accessibility
- A native modal <dialog> from the bottom edge: the platform traps focus and provides the backdrop.
- DrawerTitle names it through aria-labelledby; DrawerBody describes it. Focus moves in on open and returns on close.
- closeLabel renders a labelled close button. The ref is forwarded to the <dialog>.
Markup
<dialog class="rs-drawer" aria-labelledby="notes-title" open><button class="rs-drawer-close" type="button" aria-label="Close">×</button><h2 class="rs-drawer-title" id="notes-title">Notes</h2><p class="rs-drawer-body">A bottom panel. Escape closes it.</p></dialog>