Skip to main content
Pricing

Drawer

Free

A slide-in panel from the left or right edge of the viewport, used for secondary content, detail views, filters, navigation, and settings that supplement the main page without fully replacing it. Includes a backdrop, close button, focus trap, and scroll lock identical to Modal. Animates in with a slide transition for spatial context. Available in right, left, and bottom edge variants.

6 variants

Position
Details
NameSarah
RoleDesigner

Variants

VariantDescriptionWhen to use
RightPanel slides in from the right edge, the most common placement.Use for detail panels, editing side-sheets, and supplementary content.
LeftPanel slides in from the left edge.Use for navigation drawers and filter panels on desktop, or mobile nav menus.
BottomPanel slides up from the bottom edge.Use on mobile for filters, actions, and supplementary content.

Usage Guidelines

Do

Trap focus within the drawer while it is open.

Don't

Open a drawer from within another drawer — stack complexity confuses users.

Do

Return focus to the trigger element when the drawer closes.

Don't

Use a drawer for critical confirmations — use a Modal or Confirmation Dialog instead.

Do

Provide a clear close button and allow Escape to dismiss.

Don't

Allow the drawer to exceed 480px width on desktop without good reason — it dominates the view.

Do

Use a title that describes the drawer's content or purpose.

Don't

Use drawers for primary content — they are supplementary by nature.

Code

HTML
<div class="lex-drawer-backdrop" aria-hidden="true"></div>
<aside class="lex-drawer lex-drawer--right" role="dialog" aria-modal="true"
  aria-labelledby="drawer-title">
  <div class="lex-drawer__header">
    <h2 class="lex-drawer__title" id="drawer-title">Filters</h2>
    <button class="lex-icon-button lex-icon-button--tertiary"
      aria-label="Close drawer">
      <svg width="20" height="20" viewBox="0 0 24 24" fill="none"
        stroke="currentColor" stroke-width="2">
        <line x1="18" y1="6" x2="6" y2="18" />
        <line x1="6" y1="6" x2="18" y2="18" />
      </svg>
    </button>
  </div>
  <div class="lex-drawer__body">
    <p>Drawer content goes here.</p>
  </div>
  <footer class="lex-drawer__footer">
    <button class="lex-button lex-button--secondary lex-button--md">Reset</button>
    <button class="lex-button lex-button--primary lex-button--md">Apply</button>
  </footer>
</aside>
CSS Custom Properties
.lex-drawer-backdrop {
  position: fixed;
  inset: 0;
  background: var(--lex-drawer-backdrop-bg, rgba(0, 0, 0, 0.5));
  z-index: var(--lex-z-drawer-backdrop, 999);
}

.lex-drawer {
  position: fixed;
  top: 0;
  bottom: 0;
  width: var(--lex-drawer-width, 400px);
  max-width: 90vw;
  background: var(--lex-drawer-bg, var(--lex-bg-surface-raised));
  box-shadow: var(--lex-drawer-shadow, var(--lex-shadow-xl));
  display: flex;
  flex-direction: column;
  z-index: var(--lex-z-drawer, 1000);
  transition: transform 250ms ease;
}

.lex-drawer--right {
  right: 0;
  transform: translateX(0);
}

.lex-drawer--right[data-state="closed"] {
  transform: translateX(100%);
}

.lex-drawer--left {
  left: 0;
  transform: translateX(0);
}

.lex-drawer--left[data-state="closed"] {
  transform: translateX(-100%);
}

.lex-drawer__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--lex-space-16) var(--lex-space-24);
  border-bottom: 1px solid var(--lex-border-default);
}

.lex-drawer__title {
  font-size: var(--lex-font-size-lg);
  font-weight: 600;
  margin: 0;
}

.lex-drawer__body {
  padding: var(--lex-space-24);
  overflow-y: auto;
  flex: 1;
}

.lex-drawer__footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--lex-space-8);
  padding: var(--lex-space-16) var(--lex-space-24);
  border-top: 1px solid var(--lex-border-default);
}
React
// Using Lexicon CSS classes with React

export function FilterDrawer({ open, onClose }: { open: boolean; onClose: () => void }) {
  return (
    <Drawer open={open} onClose={onClose} side="right" title="Filters">
      <Drawer.Body>
        <p>Drawer content goes here.</p>
      </Drawer.Body>
      <Drawer.Footer>
        <Button variant="secondary" onClick={onClose}>Reset</Button>
        <Button variant="primary">Apply</Button>
      </Drawer.Footer>
    </Drawer>
  );
}

Design Tokens

TokenValue (dark)Value (light)CSS property
--lex-drawer-bgbg
--lex-drawer-widthwidth
--lex-drawer-shadowshadow
--lex-drawer-backdropbackdrop

Accessibility

  • Use role="dialog" with aria-modal="true" — same ARIA pattern as Modal.
  • Provide aria-labelledby pointing to the drawer title element.
  • Focus must be trapped inside the drawer — Tab and Shift+Tab cycle within.
  • On open, move focus to the first focusable element or the close button.
  • On close, return focus to the element that triggered the drawer.
  • Body scroll must be locked while the drawer is open.
  • Backdrop click and Escape key must both close the drawer.

Keyboard Interactions

TabMoves focus to the next focusable element within the drawer.
Shift + TabMoves focus to the previous focusable element within the drawer.
EscapeCloses the drawer and returns focus to the trigger.