Drawer
A panel attached to one edge of the window. It either floats over the page and is dismissed, or sits in the layout as a fixed sidebar: the same panel either way.
import { Button, Drawer } from 'neba';
<Drawer trigger={<Button variant="outline">Open navigation</Button>} title="Workspace">
<List>…</List>
</Drawer>;Props
| Prop | Type | Default | Description |
|---|---|---|---|
| locale | string | — | BCP 47 tag naming the × in that language. Unsupported tags fall back to English |
| side | 'top' | 'right' | 'bottom' | 'left' | 'left' | Which edge of the window the panel is attached to. Physical, as NebaSide is everywhere |
| mode | 'overlay' | 'inline' | 'overlay' | overlay floats on a scrim, holds focus and is dismissed. inline is part of the layout: no scrim, no portal, nothing to dismiss |
| sizeshared | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | The type scale, the padding, and a side panel's default width |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | Semantic colour role. The sheet is never dyed, so it reaches the edge and the focus ring |
| densityshared | 'default' | 'compact' | 'default' | Padding only |
| open | boolean | — | Whether it is shown. With onOpenChange, a controlled drawer. In inline mode it is whether the panel is in the layout |
| defaultOpen | boolean | — | The initial state of an uncontrolled drawer. false in overlay mode, true in inline mode: a fixed sidebar that had to be opened would not be fixed |
| onOpenChange | (open: boolean) => void | — | Called when it opens or closes |
| trigger | ReactElement | — | The element that opens it, wired up by Base UI. overlay only: an inline drawer has nothing to open, so it is not rendered |
| title | ReactNode | — | The heading. Rendered as the element that names the drawer |
| description | ReactNode | — | A line under the title, and the drawer's accessible description |
| actions | ReactNode | — | The bottom row, held against the foot of the panel and end-aligned. DrawerClose is what makes one of them dismiss |
| dividers | boolean | false | Separates the sections with hairlines instead of space. Worth turning on the moment the body scrolls |
| showClose | boolean | — | The × in the corner. On in overlay mode, off in inline mode: a × that closes a fixed sidebar with nothing to reopen it is a one-way door |
| closeLabel | string | — | Accessible name of the × button |
| extent | number | string | — | How far the panel reaches in from its edge: a width for left and right, a height for top and bottom. Numbers are pixels |
| rounded | boolean | true | Cuts the two corners on the edge that faces the page. The corners against the window edge are always square |
| modal | boolean | 'trap-focus' | true | Whether the page behind is taken away. trap-focus keeps it scrollable and clickable while holding focus inside. overlay only |
| dismissible | boolean | true | Whether Escape or a click on the scrim closes it. overlay only |
| children | ReactNode | — | The body: the only part that scrolls |
Native <div> attributes pass through to the panel. Only color, title and children are excluded, since the table above spells them differently.
DrawerClose is Base UI's Dialog.Close, re-exported. Give it a render prop and any element dismisses the drawer it is inside: <DrawerClose render={<Button>Cancel</Button>} />. It belongs to an overlay drawer: an inline one is not a dialog.
The shared axes are described in prop conventions.
Examples
side
side is the edge the panel is attached to. left and right take a width from the size ladder and fill the height; top and bottom fill the width and are as tall as their content, up to 85% of the window.
mode
overlay, the default, is the drawer you open: a scrim, a focus trap, Escape, and focus returned to the trigger. inline puts the same panel in the layout (no scrim, no portal, nothing to dismiss), and open decides whether it is in the flow at all. It defaults to open, so a fixed sidebar needs no state.
Because it is one component, a sidebar that becomes a hamburger at a breakpoint is a mode that changes rather than two components to swap between.
rounded
rounded cuts the two corners on the edge facing the page: the top and bottom of a side panel, the inner pair of a top or bottom one. The corners against the window edge stay square. Turn it off for a panel that should read as an extension of the window.
dividers and scrolling
The body is the only part that scrolls, so title, description and actions stay put. dividers replaces the space between the sections with hairlines, which is what says the header did not move.
extent
extent is how far the panel reaches in from its edge: a width for left and right, a height for top and bottom. Numbers are pixels, strings are CSS lengths. Left alone, a side panel takes the width its size implies.
<Drawer side="right" extent={420} title="Details" />
<Drawer side="bottom" extent="50vh" title="Filters" />Accessibility
- In
overlaymode the panel is a modal dialog: focus is trapped inside, the page behind goes inert, Escape closes it and focus returns to the trigger. titlenames the drawer anddescriptiondescribes it, wired witharia-labelledbyandaria-describedby. A drawer with neither needs anaria-labelof its own.modal="trap-focus"keeps the page scrollable and clickable while still holding focus inside.dismissible={false}cancels Escape and the click on the scrim. Give that drawer actions that close it, because there will be no other way out.- An
inlinedrawer is not a dialog: it takes no focus, traps nothing, and announces nothing. Itstitleis a plain heading, so put it in the page's heading order. localedecides the ×'s accessible name;closeLabelwrites it out instead.