Drawer
Edge-aligned dialog surface for focused workflows and contextual actions.
Drawer is a dialog-like surface that slides from an edge of the viewport. It is useful for filters, navigation, quick edits, settings, and contextual workflows where the content should remain visually connected to the current page.
Basic usage
import { Drawer } from '@mosmox/ui';
<Drawer open={open} onOpenChange={setOpen} title="Workspace settings">
<p>Update the workspace configuration.</p>
</Drawer>The default placement is right, the default size is default, and the default entrance effect is slide.
Modal and non-modal drawers
Drawers are modal by default. A modal drawer displays the shared overlay, lightly blurs the page behind it, prevents page scrolling, traps keyboard focus, and closes on Escape or overlay click when those behaviors are enabled.
<Drawer modal title="Filters" open={open} onOpenChange={setOpen}>
<FilterForm />
</Drawer>Set modal={false} for a non-modal drawer. It remains above the page and interactive, but the underlying page is not covered by an overlay and page scrolling is not locked.
<Drawer modal={false} placement="right" size="sm" title="Quick notes" open={open} onOpenChange={setOpen}>
<NotesList />
</Drawer>Placement
placement controls the edge from which the drawer is anchored: right (default), left, top, or bottom.
<Drawer placement="left" title="Navigation" open={open} onOpenChange={setOpen}>
<NavigationItems />
</Drawer>
<Drawer placement="top" title="Notifications" open={open} onOpenChange={setOpen}>
<NotificationSummary />
</Drawer>
<Drawer placement="bottom" title="Share" open={open} onOpenChange={setOpen}>
<ShareActions />
</Drawer>Right and left drawers fill the viewport height. Top and bottom drawers fill the viewport width and use their size as a height.
Sizes
There are four sizes: sm, default, lg, and xl. For left and right placement they control width; for top and bottom placement they control height.
| Size | Left/right width | Top/bottom height |
|---|---|---|
sm | 320px | 240px |
default | 420px | 360px |
lg | 640px | 520px |
xl | 80vw | 75vh |
<Drawer size="sm" title="Compact drawer" open={open} onOpenChange={setOpen} />
<Drawer size="default" title="Standard drawer" open={open} onOpenChange={setOpen} />
<Drawer size="lg" title="Large drawer" open={open} onOpenChange={setOpen} />
<Drawer size="xl" title="Extra large drawer" open={open} onOpenChange={setOpen} />On narrow screens, left and right drawers use the full viewport width, while top and bottom drawers use up to 70% of the viewport height.
Entrance animations
Use animation to control how the panel appears:
slide(default) slides from its placement edge;fadefades the panel in;scalefades and scales the panel into place;nonerenders without an entrance animation.
<Drawer animation="slide" placement="right" open={open} onOpenChange={setOpen} />
<Drawer animation="fade" placement="bottom" open={open} onOpenChange={setOpen} />
<Drawer animation="scale" placement="left" open={open} onOpenChange={setOpen} />
<Drawer animation="none" open={open} onOpenChange={setOpen} />Animations are disabled automatically when the user prefers reduced motion.
Header and close button
The standard header supports title, description, titleIcon, and closeButton. titleIcon is rendered before the title and is decorative. Set closeButton={false} to hide the built-in close button.
import { Gear } from '@mosmox-ui/icons';
<Drawer
title="Settings"
description="Manage workspace preferences."
titleIcon={<Gear size={16} />}
closeButton={false}
open={open}
onOpenChange={setOpen}
>
<SettingsForm />
</Drawer>Use header to replace the complete header, including the title, description, icon, and close button. A custom header must provide its own close control when users need to dismiss the drawer and should use aria-label when it does not contain a clear heading.
<Drawer
aria-label="Custom project drawer"
header={<CustomHeader onClose={() => setOpen(false)} />}
open={open}
onOpenChange={setOpen}
>
<ProjectDetails />
</Drawer>Footer actions
showActions displays standard Cancel and OK buttons. actions configures or disables either standard action, changes their order, alignment, size, variant, icon, loading state, and click behavior.
<Drawer
title="Save changes"
showActions
actions={{
order: ['confirm', 'cancel'],
confirm: { label: 'Save', icon: <Check size={16} />, onClick: save },
cancel: { label: 'Cancel', onClick: () => setOpen(false) },
}}
open={open}
onOpenChange={setOpen}
>
<EditForm />
</Drawer>The default action configuration is Cancel with the outline variant followed by OK with the primary variant. actions.cancel={false} or actions.confirm={false} hides one action. actions.order accepts ['cancel', 'confirm'] or ['confirm', 'cancel']. Use actionsAlign="start", "center", "end", or "between" to position the action group.
Use footerAside to add content on the opposite side of the action group. Use footer when the entire footer should be replaced with custom content.
<Drawer
title="Invite member"
footerAside={<span>Invitations are sent by email.</span>}
actions={{ align: 'end', confirm: { label: 'Invite' }, cancel: { label: 'Cancel' } }}
open={open}
onOpenChange={setOpen}
>
<InviteForm />
</Drawer>
<Drawer
title="Custom footer"
footer={<CustomFooter onDone={() => setOpen(false)} />}
open={open}
onOpenChange={setOpen}
>
<Content />
</Drawer>Controlled and uncontrolled state
Use open with onOpenChange when the parent owns visibility. Use defaultOpen for an initially open uncontrolled drawer. onClose is called whenever the drawer closes, including close button, Escape, overlay click, and default action behavior.
const [open, setOpen] = useState(false);
<Drawer
open={open}
onOpenChange={setOpen}
onClose={() => analytics.track('drawer_closed')}
title="Activity"
>
<ActivityList />
</Drawer>Dismissal and interaction
closeOnEscapedefaults totrueand controls Escape dismissal.closeOnOverlayClickdefaults totrueand controls modal overlay dismissal.modal={false}does not render an overlay; the drawer itself remains interactive.childrenis the scrollable content region, so long forms do not force the whole page to scroll.- Focus moves into the drawer when it opens and returns to the previously focused element when it closes.
- Modal drawers keep keyboard focus inside the panel while open.
API Reference
Drawer
| Property | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Scrollable drawer content. |
open | boolean | — | Controlled visibility. |
defaultOpen | boolean | false | Initial visibility for uncontrolled usage. |
onOpenChange | (open: boolean) => void | — | Called when visibility changes. |
onClose | () => void | — | Called after the drawer requests closing. |
modal | boolean | true | Render overlay, lock body scrolling, and trap focus. |
placement | 'top' | 'right' | 'bottom' | 'left' | 'right' | Viewport edge where the drawer appears. |
size | 'sm' | 'default' | 'lg' | 'xl' | 'default' | Width or height preset. xl uses 80vw for side drawers and 75vh for top/bottom drawers. |
animation | 'none' | 'slide' | 'fade' | 'scale' | 'slide' | Entrance animation. |
title | ReactNode | — | Standard header title. |
description | ReactNode | — | Supporting text below the title. |
titleIcon | ReactNode | — | Decorative icon before the title. |
header | ReactNode | — | Replaces the complete standard header. |
closeButton | boolean | true | Show the standard close button. |
closeOnEscape | boolean | true | Close when Escape is pressed. |
closeOnOverlayClick | boolean | true | Close when the modal overlay is clicked. |
actions | DrawerActions | — | Configure standard footer actions. |
showActions | boolean | false | Enable default Cancel and OK actions. |
actionsAlign | 'start' | 'center' | 'end' | 'between' | 'end' | Alignment of the action group. |
footerAside | ReactNode | — | Content on the side opposite the actions. |
footer | ReactNode | — | Replaces the complete standard footer. |
aria-label | string | — | Accessible name when no visible title is available. |
id | string | generated | Base ID used for title and description relationships. |
className | string | — | Additional class on the drawer panel. |
DrawerActions and DrawerAction
| Property | Type | Default | Description |
|---|---|---|---|
actions.cancel | DrawerAction | false | Cancel | Configure or hide the cancel action. |
actions.confirm | DrawerAction | false | OK | Configure or hide the confirm action. |
actions.order | Array<'cancel' | 'confirm'> | ['cancel', 'confirm'] | Rendering order of actions. |
actions.align | DrawerActionsAlign | actionsAlign | Per-actions alignment override. |
label | ReactNode | — | Button content. |
icon | ReactNode | — | Icon rendered before the button label. |
variant | ButtonVariant | outline / primary | Button style; cancel defaults to outline and confirm to primary. |
size | ButtonSize | component size | Button size override. |
disabled | boolean | false | Disable the action. |
loading | boolean | false | Show the button loading state. |
loadingLabel | string | — | Accessible/loading text for the button. |
onClick | (event: MouseEvent<HTMLButtonElement>) => void | closes drawer | Custom action handler. |
type | 'button' | 'submit' | 'reset' | 'button' | Native button type. |
aria-label | string | — | Accessible button name override. |
Accessibility and theme tokens
The drawer uses role="dialog", connects the standard title and description with ARIA relationships, exposes aria-modal="true" for modal mode, provides named close and action buttons, restores focus after closing, and honors reduced-motion preferences.
Shared visual values come from theme tokens and can be overridden through ConfigProvider. Drawer-only layout and animation rules live in Drawer.css.
| Token | Purpose |
|---|---|
--mm-color-surface | Drawer surface. |
--mm-color-text | Title and content text. |
--mm-color-muted-text | Description and footer-aside text. |
--mm-color-control-border | Panel and control borders. |
--mm-color-control-filled | Close-button hover surface. |
--mm-color-control-icon | Close and title icon colors. |
--mm-color-overlay | Modal overlay and page dimming. |
--mm-overlay-blur | Shared modal backdrop blur used by Drawer and Dialog. Defaults to 2px. |
--mm-color-primary | Standard confirm action and focus styling. |
--mm-color-focus | Keyboard focus ring. |
--mm-radius-control | Drawer and control corner radius. |
--mm-font-family-sans | Drawer typography. |
--mm-font-weight-medium | Drawer title weight. |
--mm-space-control-gap | Title and action spacing. |