Mosmox UI
Current version
GitHubnpmjs.com
Overlays

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.

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.

SizeLeft/right widthTop/bottom height
sm320px240px
default420px360px
lg640px520px
xl80vw75vh
<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;
  • fade fades the panel in;
  • scale fades and scales the panel into place;
  • none renders 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>

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

  • closeOnEscape defaults to true and controls Escape dismissal.
  • closeOnOverlayClick defaults to true and controls modal overlay dismissal.
  • modal={false} does not render an overlay; the drawer itself remains interactive.
  • children is 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

PropertyTypeDefaultDescription
childrenReactNode—Scrollable drawer content.
openboolean—Controlled visibility.
defaultOpenbooleanfalseInitial visibility for uncontrolled usage.
onOpenChange(open: boolean) => void—Called when visibility changes.
onClose() => void—Called after the drawer requests closing.
modalbooleantrueRender 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.
titleReactNode—Standard header title.
descriptionReactNode—Supporting text below the title.
titleIconReactNode—Decorative icon before the title.
headerReactNode—Replaces the complete standard header.
closeButtonbooleantrueShow the standard close button.
closeOnEscapebooleantrueClose when Escape is pressed.
closeOnOverlayClickbooleantrueClose when the modal overlay is clicked.
actionsDrawerActions—Configure standard footer actions.
showActionsbooleanfalseEnable default Cancel and OK actions.
actionsAlign'start' | 'center' | 'end' | 'between''end'Alignment of the action group.
footerAsideReactNode—Content on the side opposite the actions.
footerReactNode—Replaces the complete standard footer.
aria-labelstring—Accessible name when no visible title is available.
idstringgeneratedBase ID used for title and description relationships.
classNamestring—Additional class on the drawer panel.

DrawerActions and DrawerAction

PropertyTypeDefaultDescription
actions.cancelDrawerAction | falseCancelConfigure or hide the cancel action.
actions.confirmDrawerAction | falseOKConfigure or hide the confirm action.
actions.orderArray<'cancel' | 'confirm'>['cancel', 'confirm']Rendering order of actions.
actions.alignDrawerActionsAlignactionsAlignPer-actions alignment override.
labelReactNode—Button content.
iconReactNode—Icon rendered before the button label.
variantButtonVariantoutline / primaryButton style; cancel defaults to outline and confirm to primary.
sizeButtonSizecomponent sizeButton size override.
disabledbooleanfalseDisable the action.
loadingbooleanfalseShow the button loading state.
loadingLabelstring—Accessible/loading text for the button.
onClick(event: MouseEvent<HTMLButtonElement>) => voidcloses drawerCustom action handler.
type'button' | 'submit' | 'reset''button'Native button type.
aria-labelstring—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.

TokenPurpose
--mm-color-surfaceDrawer surface.
--mm-color-textTitle and content text.
--mm-color-muted-textDescription and footer-aside text.
--mm-color-control-borderPanel and control borders.
--mm-color-control-filledClose-button hover surface.
--mm-color-control-iconClose and title icon colors.
--mm-color-overlayModal overlay and page dimming.
--mm-overlay-blurShared modal backdrop blur used by Drawer and Dialog. Defaults to 2px.
--mm-color-primaryStandard confirm action and focus styling.
--mm-color-focusKeyboard focus ring.
--mm-radius-controlDrawer and control corner radius.
--mm-font-family-sansDrawer typography.
--mm-font-weight-mediumDrawer title weight.
--mm-space-control-gapTitle and action spacing.