// preview in
saved for every page

Drawer

A panel that slides in from an edge of the screen for a task that keeps the page in view, like filters, details or a cart. It is modal; for a short decision, use a dialog.

Built on Base UIPattern WAI-ARIA Dialog (Modal)Client componentSource ↗
minimal · light · #3fa34d
side

Installation

Set up Camaleo once (Installation), then import what you need. It renders on the client.

import { Drawer, DrawerTrigger, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerBody, DrawerFooter, DrawerClose } from "@camaleo/react";

Anatomy

DrawerContent renders the backdrop and the sheet in a portal, with the skin, mode and brand of the page around the trigger. Only the body scrolls, so the header and the footer always stay in view.

Examples

Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above. Each one starts closed: open it, then try Tab, Escape and a click on the backdrop.

minimal · light · #3fa34d
Order #4821Padaria Estrela · 03/14/2026Out for delivery$186.40

Sides

Right for details and filters, next to the content they change. Left for navigation, where the menu lives. Bottom on phones, where the thumb reaches.

Pick a page in the menu.

Navigation on a phone

Click a link. The menu closes, focus returns to the menu button and a status message says which page is now showing.

12 products
  • Mogiana beans, 250 g$18
  • Mate tea, 200 g$14
  • Gooseneck kettle$89

Filters with Apply and Reset

Change some filters. The main button counts the results as you go; nothing changes on the page until you press it, and closing without it discards the changes.

Step 3 of 3Review and subscribe
Pro planBilled monthly, cancel anytime$12/mo
Terms of serviceUpdated on 02/01/2026

Long content, actions in view

Only the body scrolls, so Accept and Decline stay in the footer while people read. A body with only text becomes a tab stop while it scrolls, so keyboard users can scroll it too.

Email notifications2 of 3 on

Named by its title

DrawerTitle is the dialog's name and DrawerDescription is read after it. The × has no text, so pass aria-label in the page's language.

Q3 reportEdited today by Ana

Bottom sheet

A short list of choices for phones. Each one closes the sheet and reports what happened in a status message.

Team3 people
  • Ana RibeiroProduct designer
  • João LimaBackend engineer
  • Carla SouzaAccount manager

Opened from code

Without a DrawerTrigger, control it with open and onOpenChange. Focus still goes back to the button that opened it.

In context

These are whole pieces of UI you can copy, in the skin you picked.

Grão Coffee Co.
Best sellers
  • Cerrado beans, 500 g$38.00
  • Stoneware mug$59.00
Shopping cartDrawer · Card · SearchInput · IconButton · Separator
Billbox
Good morning, Ana
This week$4,820.00
Open invoices12
Notification centerDrawer · IconButton · Badge · Avatar · Button
In progress

Update the price list for April

Due 04/01/2026 · Ana Ribeiro

Task detailsCard · Drawer · Field · Textarea · Checkbox

Guidelines

DoA task that needs the page in view.

Editing an address next to the order it belongs to keeps people oriented.

Don’tA drawer for a yes or no.

A whole sheet for one sentence and two buttons is too much. Use an alert dialog, which also puts focus on the safe choice.

DoActions in the footer.

DrawerFooter stays in view while the list scrolls, so the next step is always there.

Don’tActions at the end of a long body.

People have to scroll past 24 options to find out how to confirm.

Props

PropTypeDefaultDescription
side"bottom" | "left" | "right""right"Edge the sheet comes from. Swiping toward that edge closes it.
openboolean—Whether the drawer is currently open.
defaultOpenbooleanfalseWhether the drawer is initially open. To render a controlled drawer, use the open prop instead.
modal"trap-focus" | booleantrueDetermines if the drawer enters a modal state when open. - true: user interaction is limited to just the drawer: focus is trapped, document page scroll is locked, and pointer interactions on outside elements are disabled. - false: user interaction with the rest of the document is allowed. - 'trap-focus': focus is trapped inside the drawer, but document page scroll is not locked and pointer interactions outside of it remain enabled.
onOpenChange(open, eventDetails) => void—Event handler called when the drawer is opened or closed.
disablePointerDismissalbooleanfalseWhether to prevent the drawer from closing on outside presses. For non-modal drawers, this also prevents the drawer from closing when focus moves outside of it.

DrawerContent takes the popup's props, including initialFocus and finalFocus to choose where focus goes when it opens and closes. DrawerClose without children renders the × button; pass aria-label in the page's language (default "Close").

Styling hooks

Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the sheet itself.

Attributes

data-swipe-directionright · left · down
data-open · data-closedon the popup and the backdrop
data-starting-style · data-ending-stylewhile it slides in and out
data-swipingpresent while it is dragged

Tokens it reads

Color

--cml-border--cml-dialog-border--cml-fg--cml-fg-muted--cml-focus-offset--cml-focus-ring--cml-focus-width--cml-radius-surface--cml-shadow-surface--cml-surface-raised

Shape & size

--cml-border-width--cml-gap--cml-radius-control

Type & motion

--cml-duration-base--cml-ease-standard--cml-font-display--cml-font-weight-display

This component only

--cml-backdrop--cml-item-highlight
/* a wider sheet for the order editor */
.orders [data-slot="drawer-popup"][data-swipe-direction="right"] {
  inline-size: min(36rem, calc(100vw - 3rem));
}

Accessibility

EnterOn the trigger, opens the drawer and moves focus to the sheet.
TabMoves to the next control in the drawer. From the last one it goes back to the first; the page behind can't be reached.
Shift + TabMoves to the previous control, also wrapping inside the drawer.
Page Down / Page UpScroll a long body once it has focus. A body that scrolls and holds no controls is a tab stop of its own.
EscapeCloses the drawer and returns focus to the element that opened it.
Was this page helpful? · Edit this page on GitHub