Side-anchored panels for detail without losing place.
| Prop | Type | Default | Description |
|---|---|---|---|
| open | boolean | false | Controlled visibility |
| onClose | (reason) => void | — | Escape, scrim click, or close button |
| side | right · left | right | Which edge it slides from |
| width | narrow · default · wide | default | Panel width; caps at 80% of the viewport |
| title | string | — | Labels the panel for assistive tech |
| dismissable | boolean | true | When false, only an action closes it |
| Trigger | Result |
|---|---|
| Escape | Closes the drawer |
| Scrim click | Closes; clicks inside the panel do not |
| Tab / Shift+Tab | Focus is trapped within the panel |
| On open / close | Focus moves in, then back to the trigger |
Reach for a drawer over a modal when the user needs the page's context to stay put — editing a row while the list stays visible, or reading detail without losing scroll position. A modal interrupts; a drawer accompanies.
Use a drawer when keeping the page's context in view helps — editing beside the list.
Don't use a drawer for a single yes/no. That interruption belongs to a modal.
Keep the primary action sticky in the footer, always reachable however long the body scrolls.
Never stack drawers on drawers. If a step needs another step, it wants a full page.
The list stays put on the left; detail arrives on the right. No context lost.
On small screens the drawer becomes the primary navigation, sliding from the left.
A few fields with a sticky Save — longer than a modal wants, shorter than a page.
| Token | Value | Role |
|---|---|---|
| --drawer-w | min(320px, 80%) | Panel width, capped to the viewport |
| --drawer-bg | #101114 | Panel surface |
| --drawer-border | #23242A | Inner-edge hairline |
| --drawer-shadow | -18px 0 50px rgba(0,0,0,.5) | Cast toward the page (flips for left) |
| --scrim | rgba(4,4,6,.62) | Backdrop dim |
| --drawer-foot-border | #1E1F24 | Sticky footer hairline |
| --drawer-ease | cubic-bezier(.22,1,.36,1) | Slide, 0.3s |
<div class="o-drawer-scrim is-open">
<aside class="o-drawer o-drawer--right" role="dialog" aria-modal="true">
<div class="o-drawer__head">
<div>
<h2 class="o-drawer__title">Button · v3.2</h2>
<p class="o-drawer__sub">Forms · 8 variants</p>
</div>
<button class="o-drawer__close" aria-label="Close">✕</button>
</div>
<div class="o-drawer__body"><!-- scrolls --></div>
<div class="o-drawer__foot">
<button class="o-btn o-btn--ghost">Cancel</button>
<button class="o-btn o-btn--primary">Save</button>
</div>
</aside>
</div>