Drawer
A panel that slides in from the left or right edge of the viewport. Distinct from BottomSheet (which slides up on mobile) — Drawer is a desktop-first side panel for settings, filters, or detail views.
Preview
Motion
| Element | Enter | Exit |
|---|---|---|
| Backdrop | opacity 0 → 1 (200ms ease-out) | opacity 1 → 0 (200ms ease) |
| Panel (right) | translateX(100%) → 0 (250ms ease) | 0 → translateX(100%) (250ms ease) |
| Panel (left) | translateX(-100%) → 0 (250ms ease) | 0 → translateX(-100%) (250ms ease) |
Drawer vs Bottom Sheet
| Drawer | Bottom Sheet | |
|---|---|---|
| Primary axis | Horizontal (left/right) | Vertical (bottom) |
| Primary context | Desktop — settings, filters, detail panels | Mobile — action sheets, quick options |
| Content height | Full viewport height | Partial (snap points) |
| Width | Fixed, configurable | Full width |
Implementation
DrawerUsage.tsx
tsx
"use client";
import { useState } from "react";
import { Drawer } from "@/components/ui/Drawer";
import { Button } from "@/components/ui/Button";
export function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>Open drawer</Button>
<Drawer
open={open}
onClose={() => setOpen(false)}
title="Settings"
side="right"
width={400}
>
<p>Drawer content goes here.</p>
</Drawer>
</>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
open* | boolean | — | Controls whether the drawer is visible. |
onClose* | () => void | — | Called when the user presses Escape, clicks the backdrop, or presses the close button. |
side | "left" | "right" | "right" | Which edge the drawer slides in from. |
width | string | number | 360 | Width of the drawer panel. Accepts a pixel number or any valid CSS width string. |
title | React.ReactNode | — | Title shown in the drawer header. Also used as aria-label when it is a string. |
children* | React.ReactNode | — | Scrollable body content. |
ARIA roles
| Element | Role | Key attributes |
|---|---|---|
| Panel | dialog | aria-modal='true', aria-label (from title prop) |
| Close button | button (implicit) | aria-label='Close drawer' |
| Backdrop | (decorative) | aria-hidden='true' |
Keyboard
| Key | Action |
|---|---|
| Escape | Close the drawer and restore focus to the trigger element |
| Tab | Cycle forward through focusable elements inside the drawer (focus trapped) |
| Shift+Tab | Cycle backward through focusable elements inside the drawer (focus trapped) |
Accessibility
- →Panel uses role="dialog" and aria-modal="true" — assistive technologies treat it as a modal dialog.
- →Focus moves to the close button on open and returns to the trigger element on close.
- →Escape closes the drawer — consistent with other overlay components (Modal, CommandPalette).
- →Backdrop click also closes the drawer — both dismiss methods are equally supported.
- →The title prop is used as aria-label when it is a string, providing an accessible name for the dialog.