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.
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.
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.
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.
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.
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.
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.
Bottom sheet
A short list of choices for phones. Each one closes the sheet and reports what happened in a status message.
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.
Update the price list for April
Due 04/01/2026 · Ana Ribeiro
Guidelines
Editing an address next to the order it belongs to keeps people oriented.
A whole sheet for one sentence and two buttons is too much. Use an alert dialog, which also puts focus on the safe choice.
DrawerFooter stays in view while the list scrolls, so the next step is always there.
People have to scroll past 24 options to find out how to confirm.
Props
open prop instead.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.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
Tokens it reads
Color
Shape & size
Type & motion
This component only
/* a wider sheet for the order editor */
.orders [data-slot="drawer-popup"][data-swipe-direction="right"] {
inline-size: min(36rem, calc(100vw - 3rem));
}Accessibility
- The sheet is a modal dialog: the page behind is hidden from screen readers, and a click on the backdrop closes the drawer.
- DrawerTitle gives the dialog its name. Every drawer needs one, even if it repeats the trigger's label.
- When it closes, focus goes back to the trigger, or to the button that opened it from code. Report results in a status message, because the drawer that showed them is gone.
- Swiping toward its edge closes it on touch screens. Every swipe has a button alternative: the ×, a footer action or Escape.