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
npm run storybookWhen 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:
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: Savecontent.action.cancel: Not nowcontent.action.destroy: Removecontent.action.retry: Try againcontent.action.empty-action: Browse guides
See Voice and tone for the whole voice.
Accessibility and keyboard
| Keyboard | What happens | Not |
|---|---|---|
| undefined | Escape 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.
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.
| Part | Its token and the semantic it aliases | Value |
|---|---|---|
| Shared | ||
| Transition panels duration | component.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 | ||
| Corner | component.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 | ||
| Gap | component.drawer.header.gapcomponent-spacing-map.hairlinevar(--drawer-header-gap) | space.2:0.125rem |
| Padding | component.drawer.header.paddingspace.inset.containervar(--drawer-header-padding) | space.24:1.5rem |
| Gap 2 | component.drawer.header.gap-2component-spacing-map.x2var(--drawer-header-gap-2) | space.8:0.5rem |
| Footer | ||
| Gap | component.drawer.footer.gapspace.gap.defaultvar(--drawer-footer-gap) | space.16:1rem |
| Padding | component.drawer.footer.paddingspace.inset.containervar(--drawer-footer-padding) | space.24:1.5rem |
| Title | ||
| Weight | component.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 size | component.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 |
| Corner | radius.container straight from the system, through --radius-lg | radius.12 · 0.75rem (12px) |