Skip to main content

Drawer

A panel that slides up from the bottom, for phones.

Left out of Content first Content first keeps few components, covered by today's pages. Sheet already holds the phone menu, and forms are rare and short. Use instead: Sheet, Dialog

See it live​

Start Storybook to see it live: npm run storybook

When to use it​

  • Use it when: Filters, details or a short form on a phone, while the page stays partly in view.
  • Not when: A blocking decision (dialog), or a wide screen where a side panel fits better (sheet).

Instead:

  • Sheet: Slides from an edge on wide screens.
  • Dialog: A blocking decision.

Words​

  • Write to one person: "you", not "users".
  • Short sentences with room to breathe.
  • Explain why before asking for anything.
  • Use: Read, Save for later, You, Next issue. Avoid: Click here, Submit, Users, Content.

The labels components use, from the voice tokens:

  • content.action.commit: Save
  • content.action.cancel: Not now
  • content.action.destroy: Remove
  • content.action.retry: Try again
  • content.action.empty-action: Browse guides

See Voice and tone for the whole voice.

Accessibility and keyboard​

KeyboardWhat happensNot
undefinedEscape closes it.undefined

Test before you ship:

  • It behaves as a dialog: named, focus moves in and back. undefined

Use it in code​

npx open-ds add drawer

The component reads only component keys and semantic tokens, so it follows the brand and mode on <html>.

Tokens it reads​

Drawer reads 14 tokens of its own, each an alias of a semantic token. Each row is one part: its own token, the semantic it aliases, and the value it ends at. Change a semantic and every component that reads it follows; override one of Drawer's tokens to change Drawer alone.

Changes with theme

Content first decides what changes when you switch theme. Here a switch changes colors only, so the marked rows differ between light and dark; everything else holds steady.

PartIts token and the semantic it aliasesValue
Shared
Transition panels durationcomponent.transition.panels-durationmotion.duration.panelsvar(--transition-panels-duration)duration.relaxed:320ms
Overlay
Fill
Changes with theme
component.drawer.overlay.backgroundcolor.surface.scrimvar(--drawer-overlay-background)Lightblack.light.scrim:#0000008CDarkblack.dark.scrim:#000000B3
Content
Fill
Changes with theme
component.drawer.content.backgroundcolor.surface.canvasvar(--drawer-content-background)Lightstone.light.50:#FFFDF8Darkstone.dark.0:#14110D
Drawer
Cornercomponent.drawer.radiusradius.roundvar(--drawer-radius)radius.full:9999px
Fill
Changes with theme
component.drawer.backgroundcolor.surface.sunkenvar(--drawer-background)Lightstone.light.100:#F6F2EADarkstone.dark.100:#2E2A24
Header
Gapcomponent.drawer.header.gapcomponent-spacing-map.hairlinevar(--drawer-header-gap)space.2:0.125rem
Paddingcomponent.drawer.header.paddingspace.inset.containervar(--drawer-header-padding)space.24:1.5rem
Gap 2component.drawer.header.gap-2component-spacing-map.x2var(--drawer-header-gap-2)space.8:0.5rem
Footer
Gapcomponent.drawer.footer.gapspace.gap.defaultvar(--drawer-footer-gap)space.16:1rem
Paddingcomponent.drawer.footer.paddingspace.inset.containervar(--drawer-footer-padding)space.24:1.5rem
Title
Weightcomponent.drawer.title.font-weighttype.title.font-weightvar(--drawer-title-font-weight)font.weight.600:600
Text
Changes with theme
component.drawer.title.labelcolor.text.primaryvar(--drawer-title-label)Lightstone.light.900:#1F1B16Darkstone.dark.900:#FFFDF8
Description
Text sizecomponent.drawer.description.font-sizetype.body.font-sizevar(--drawer-description-font-size)font.size.18:1.125rem
Text
Changes with theme
component.drawer.description.labelcolor.text.mutedvar(--drawer-description-label)Lightstone.light.600:#61594DDarkstone.dark.400:#A39A8B
From the system
Edge
Changes with theme
color.border.default
straight from the system, through --border
Lightstone.light.200 · #E4DDD0Darkstone.dark.100 · #2E2A24
Cornerradius.container
straight from the system, through --radius-lg
radius.12 · 0.75rem (12px)