Drawer
ProA panel that slides in from an edge of the screen over the page, for a task or detail that doesn't need a full dialog.
Version 5.2.0· other versions
This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).
- 5.1.0
- 5.0.0
- 4.5.0
- 4.4.0
- 4.3.0
- 4.2.0
- 4.1.0
- 4.0.2
- 4.0.1
- 4.0.0
- 3.0.0
- 2.10.0
- 2.9.0
- 2.8.0
- 2.7.0
- 2.6.2
- 2.6.1
- 2.6.0
- 2.5.1
- 2.5.0
- 2.4.0
- 2.3.0
Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0 and 2.2.0: the page came later, or had another name then.
Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.
Preview
Loading the preview
Install
First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/drawer
Brings in: dialog, heading-scope, layer, locale, motion, tokens, utils · npm: @base-ui/react@^1.9.0, lucide-react@^1.67.0
Usage
"use client"
import * as React from "react"
import { Button } from "@/components/candor/ui/button"
import { Input } from "@/components/candor/ui/input"
import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger } from "@/components/candor/ui/drawer"
export default function Example() {
const [name, setName] = React.useState("Nightly export")
const [saved, setSaved] = React.useState("Nightly export")
const [open, setOpen] = React.useState(false)
const [asked, setAsked] = React.useState(false)
const dirty = name !== saved
return (
// Dragging down, Escape, a click outside and × all ask first; with unsaved changes the drawer stays and says why.
<Drawer
open={open}
onOpenChange={(next) => {
setOpen(next)
if (!next) setAsked(false)
}}
onDismissAttempt={() => {
if (!dirty) return
setAsked(true)
return false
}}
>
<DrawerTrigger render={<Button variant="outline">Edit job</Button>} />
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Edit job</DrawerTitle>
<DrawerDescription>Changes apply to the next run.</DrawerDescription>
</DrawerHeader>
<div className="flex flex-col gap-2 px-4">
<Input aria-label="Job name" value={name} onChange={(e) => setName(e.target.value)} />
{asked && (
<p role="alert" className="text-sm text-destructive">
You have unsaved changes. Save them, or discard them to close.
</p>
)}
</div>
<DrawerFooter>
<Button
onClick={() => {
setSaved(name)
setAsked(false)
setOpen(false)
}}
>
Save
</Button>
<DrawerClose render={
<Button variant="outline" onClick={() => setName(saved)}>
Discard changes
</Button>
} />
</DrawerFooter>
</DrawerContent>
</Drawer>
)
}<Drawer>
| Prop | Type | Default | Description |
|---|---|---|---|
| onOpenChange | ((open: boolean, details?: DrawerPrimitive.Root.ChangeEventDetails | undefined) => void) | undefined | — | Called when the drawer opens or closes, with Base UI's details when Base UI closed it (Escape, a click outside). |
| onDismissAttempt | ((attempt: { reason: DrawerDismissReason }) => boolean | void) | undefined | — | Called when the person tries to close the drawer by Escape, a click outside, the close button or a drag, before it closes. Return `false` to keep it open (and tell the person why, in your own UI); anything else lets it close. Your own `DrawerClose` buttons and a change to `open` are not attempts and always take effect. |
Also takes the props of Omit<DrawerPrimitive.Root.Props, "modal" | "onOpenChange">.
<DrawerClose>
A control that closes the drawer; event.preventDefault() in its onClick keeps it open, as on Radix.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | undefined | — | Classes for the part, merged with Candor's. |
| style | React.CSSProperties | undefined | — | Inline styles for the part, merged over its motion chain. |
Also takes the props of Omit<P, "className" | "style">.
<DrawerContent>
The drawer's panel, with its backdrop, the close button and grab bar, dragging to close and Tab kept inside it. Content inside sits under the title, an <h2>: it is in a HeadingScope of 2, so Candor items in it draw their headings from <h3>.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | undefined | — | Classes for the part, merged with Candor's. |
| style | React.CSSProperties | undefined | — | Inline styles for the part, merged over its motion chain. |
| side | "top" | "right" | "bottom" | "left" | "start" | "end" | undefined | "bottom" | The edge the drawer slides in from, and the direction it is dragged to close. Defaults to bottom. `"start"` and `"end"` follow the reading direction (`start` is the left edge in LTR and the right edge in RTL); `"left"` and `"right"` are always that edge. |
| showHandle | boolean | undefined | — | Shows the grab bar on the edge facing the page. Defaults to true for a drawer from the top or bottom. |
| showCloseButton | boolean | undefined | true | Shows the close (×) button. Defaults to true. |
| unsaved | string | undefined | — | What a dismissal would lose, in a few words ("your note"): while set, a dismissal (a drag past the threshold too) asks first, in a bar in the drawer. |
Also takes the props of Omit<P, "className" | "style">.
<DrawerDescription>
The sentence under the title that says what the drawer is for, read with its name.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | undefined | — | Classes for the part, merged with Candor's. |
| style | React.CSSProperties | undefined | — | Inline styles for the part, merged over its motion chain. |
Also takes the props of Omit<P, "className" | "style">.
<DrawerFooter>
The block at the bottom of the drawer for its actions.
No props.
Also takes the props of React.ComponentProps<"div">.
<DrawerHandle>
The grab bar. It is decoration for pointers: the drawer closes from the keyboard with Escape or the × button.
No props.
Also takes the props of React.ComponentProps<"div">.
<DrawerHeader>
The block at the top of the drawer for its title and description.
No props.
Also takes the props of React.ComponentProps<"div">.
<DrawerOverlay>
The backdrop that dims the page behind the drawer. While the panel is dragged toward its edge, the backdrop lightens with it, painted from the pointer (a share from 0 to 1 the panel sets on it as it moves).
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | undefined | — | Classes for the part, merged with Candor's. |
| style | React.CSSProperties | undefined | — | Inline styles for the part, merged over its motion chain. |
Also takes the props of Omit<P, "className" | "style">.
<DrawerPortal>
Puts the drawer's overlay and panel at the end of the page, or in a Candor host inside another library's dialog.
No props.
Also takes the props of Omit<DrawerPrimitive.Portal.Props, "container">.
<DrawerSwipeArea>
A strip along the edge a drawer lives on, where a swipe in from that edge opens it, as on a phone. Only for a coarse pointer (a finger): it isn't drawn for a mouse or while the drawer is open, it is hidden from screen readers and takes no focus, and the drawer's trigger stays the way in for everyone. Put it inside the Drawer, with the same side as its content.
The strip is 12 px deep and sits over the page's edge. A tap on it that isn't a swipe goes through to what is under it, but a scroll that starts on it doesn't scroll the page, so keep scrolling content and small controls (a tab bar's labels, a Send button) clear of that edge, or leave the swipe area out.
| Prop | Type | Default | Description |
|---|---|---|---|
| side | "top" | "right" | "bottom" | "left" | "start" | "end" | undefined | "bottom" | The edge the drawer comes from, as on `DrawerContent`: a swipe in from it opens the drawer. |
| className | string | undefined | — | Classes for the strip, merged with Candor's. |
<DrawerTitle>
The drawer's heading, an <h2>, which names it to screen readers.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | undefined | — | Classes for the part, merged with Candor's. |
| style | React.CSSProperties | undefined | — | Inline styles for the part, merged over its motion chain. |
Also takes the props of Omit<P, "className" | "style">.
<DrawerTrigger>
The control that opens the drawer; focus returns to it when the drawer closes.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | undefined | — | Classes for the part, merged with Candor's. |
| style | React.CSSProperties | undefined | — | Inline styles for the part, merged over its motion chain. |
Also takes the props of Omit<P, "className" | "style">.
Strings
The fixed words, in English by default. Change any of them under drawer in <LocaleProvider strings={{ … }}>; the table is exported as drawerStrings.
| Key | English |
|---|---|
| close | Close |
Motion
| Moment | Family | What it does |
|---|---|---|
| nudge | attention | One ring round the drawer when a press outside is kept from closing it. |
| open | overlay, slide | The panel slides in from its edge over the base duration and out over the quick one, with the overlay fading beside it. The edge it comes from is where it lives. Under the calm level (reduced motion, or `data-motion="calm"`) it fades in place instead of sliding. |
| release | move | The panel returns to its edge after a drag that stopped short of the threshold. |
Set with --cd-motion-drawer-<moment>-* in your brand file; the families are in the design reference.
Types
- DrawerDismissReason
- The ways a person can try to close a drawer without one of the app's own buttons.
"escape" | "outside" | "drag" | "close"