Pickers and overlays
Drawer
An edge-anchored modal panel that slides in and can be swiped away.
When to use it: Use it for secondary tasks such as settings, filters, or a cart that belong beside the page rather than over its center.
Example
import { Button, Drawer, DrawerContent, DrawerTrigger } from "@comp0/react";
import { XMarkIcon } from "@heroicons/react/20/solid";
export function Example() {
return (
<Drawer side="right">
<DrawerTrigger className="rounded bg-teal-700 px-3 py-2.5 text-base text-white sm:py-2 sm:text-sm dark:bg-teal-400 dark:text-zinc-950">
Open settings
</DrawerTrigger>
<DrawerContent
aria-labelledby="drawer-settings-title"
className="fixed m-0 h-dvh max-h-none w-[min(22rem,calc(100vw-2rem))] border-0 bg-white p-5 text-zinc-900 opacity-100 shadow-2xl ring-1 ring-zinc-950/10 transition-[opacity,translate] duration-150 ease-out data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:left-auto data-[side=right]:translate-x-0 data-[side=right]:starting:translate-x-4 starting:opacity-0 data-dragging:transition-none motion-reduce:transition-none backdrop:bg-zinc-950/40 dark:bg-zinc-900 dark:text-zinc-50 dark:shadow-none dark:ring-white/10"
>
<div className="flex h-full flex-col gap-5">
<div className="flex items-start justify-between gap-4">
<div className="grid gap-1">
<h2 id="drawer-settings-title" className="text-lg font-semibold">
Settings
</h2>
<p className="text-base text-zinc-600 sm:text-sm dark:text-zinc-400">
Drag the panel toward the right edge to dismiss it, or press Escape.
</p>
</div>
<form method="dialog">
<Button
type="submit"
aria-label="Close settings"
className="rounded p-2 text-zinc-500 outline-teal-600 hover:bg-zinc-100 focus-visible:outline-2 dark:text-zinc-400 dark:outline-teal-400 dark:hover:bg-zinc-800"
>
<XMarkIcon className="size-5" aria-hidden="true" />
</Button>
</form>
</div>
<div className="grid gap-3 text-base sm:text-sm">
<label className="flex items-center justify-between gap-4">
Product updates
<input type="checkbox" defaultChecked className="size-4 accent-teal-700" />
</label>
<label className="flex items-center justify-between gap-4">
Weekly digest
<input type="checkbox" className="size-4 accent-teal-700" />
</label>
</div>
</div>
</DrawerContent>
</Drawer>
);
}Anatomy
Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.
A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.
Drawer
Wrapper-free provider for the drawer's open state and anchored side. Does not add a DOM element.
DrawerTrigger
Button that opens the drawer. Owns a DOM element.
DrawerContent
Native modal panel anchored to one edge; dragging it toward that edge dismisses it. Owns a DOM element.
Step by step
- 1
Add the main part
Start Drawer with DrawerTrigger and pick the side the panel anchors to.
- 2
Add the supporting parts
Put a labelled DrawerContent after the trigger and anchor its styles with the data-side attribute.
- 3
Make the behavior clear
Keep an explicit close action inside; dragging the panel toward its edge or pressing Escape also dismisses it.
Exampletsx <Drawer side="right"> <DrawerTrigger>Open settings</DrawerTrigger> <DrawerContent aria-labelledby="settings-title"> <h2 id="settings-title">Settings</h2> </DrawerContent> </Drawer>;
Keyboard
- Esc
- Closes and restores trigger focus.
- ⇥
- Cycles inside the modal panel.
Forms and accessibility
Forms inside DrawerContent submit normally; method=dialog closes the drawer without navigation.
Accessibility checklist
- Connect DrawerContent to a visible heading with aria-labelledby.
- The swipe gesture is purely additive; keep an explicit close action for keyboard and assistive users.
- Focus stays inside the modal panel while it is open and returns to the trigger on close.
API reference
import { Drawer, DrawerContent, DrawerTrigger } from "@comp0/react";Drawer
Context onlyWrapper-free provider for the drawer's open state and anchored side.
| Prop | Type | Description |
|---|---|---|
open | boolean | Controlled open state. |
defaultOpen | boolean | Initial open state. |
onToggle | (open: boolean) => void | Receives the next open state. |
side | "left" | "right" | "top" | "bottom" | Edge the panel anchors to; defaults to "right". |
DrawerTrigger
DOM elementButton that opens the drawer.
| Prop | Type | Description |
|---|---|---|
as | ElementType | Fragment | Fragment merges the trigger onto your own element child. |
DrawerContent
DOM elementNative modal panel anchored to one edge; dragging it toward that edge dismisses it.
| Prop | Type | Description |
|---|---|---|
aria-labelledby | string | Points to the drawer's visible heading. |
portal | boolean | Renders into document.body; on by default. |
closedby | "any" | "closerequest" | "none" | Native dismissal policy; any adds light dismiss where supported. |
Style hooks
Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.
DrawerTrigger
| Style hook | Meaning |
|---|---|
[data-open] | The drawer is open. |
DrawerContent
| Style hook | Meaning |
|---|---|
[data-open] | The drawer is open. |
[data-side] | The anchored edge, for positioning and slide transitions. |
[data-dragging] | A dismiss drag is following the pointer. |