Drawer
A full-height modal panel anchored to the start or end edge; a native dialog on the overlay surface with a 1px overlay border on its inner edge.
stable · light DOM · 4.0.0. Light DOM retains static content, native semantics, label relationships and host composition without duplicating controls.
Live variants
start
Example destination: dw foundations
Example destination: dw actions
Example destination: dw forms
Example destination: dw feedback
<j3w1-drawer id="example-drawer-start">
<dialog class="drawer drawer-start" aria-labelledby="example-drawer-start-dw-nav-title">
<div class="drawer-header">
<h2 class="drawer-title" id="example-drawer-start-dw-nav-title">Sections</h2>
<button class="drawer-close" type="button" aria-label="Close">✕</button>
</div>
<nav class="drawer-body" aria-label="Sections">
<ul class="drawer-list">
<li class="drawer-item"><a class="drawer-link" href="#example-drawer-start-dw-foundations" aria-current="page">Foundations</a></li>
<li class="drawer-item"><a class="drawer-link" href="#example-drawer-start-dw-actions">Actions</a></li>
<li class="drawer-item"><a class="drawer-link" href="#example-drawer-start-dw-forms">Forms</a></li>
<li class="drawer-item"><a class="drawer-link" href="#example-drawer-start-dw-feedback">Feedback</a></li>
</ul>
</nav>
<p class="drawer-trap-note">Focus is trapped: Tab cycles inside the drawer; Escape closes it.</p>
</dialog>
<button class="button" type="button" data-open="">Open drawer</button><p id="example-drawer-start-dw-foundations" tabindex="-1" class="j3w1-example-target">Example destination: dw foundations</p><p id="example-drawer-start-dw-actions" tabindex="-1" class="j3w1-example-target">Example destination: dw actions</p><p id="example-drawer-start-dw-forms" tabindex="-1" class="j3w1-example-target">Example destination: dw forms</p><p id="example-drawer-start-dw-feedback" tabindex="-1" class="j3w1-example-target">Example destination: dw feedback</p></j3w1-drawer>end
<j3w1-drawer id="example-drawer-end">
<dialog class="drawer drawer-end" aria-labelledby="example-drawer-end-dw-filter-title">
<div class="drawer-header">
<h2 class="drawer-title" id="example-drawer-end-dw-filter-title">Filters</h2>
<button class="drawer-close" type="button" aria-label="Close">✕</button>
</div>
<div class="drawer-body">
<p class="drawer-text">Narrow the table to the rows that match every filter.</p>
<ul class="drawer-list">
<li class="drawer-item"><label class="drawer-check"><input class="drawer-checkbox" type="checkbox" checked="" name="example-drawer-end-value"> Only R1</label></li>
<li class="drawer-item"><label class="drawer-check"><input class="drawer-checkbox" type="checkbox" name="example-drawer-end-value"> Only tested</label></li>
<li class="drawer-item"><label class="drawer-check"><input class="drawer-checkbox" type="checkbox" name="example-drawer-end-value"> Hide deprecated</label></li>
</ul>
</div>
<p class="drawer-trap-note">Focus is trapped: Tab cycles inside the drawer; Escape closes it.</p>
</dialog>
<button class="button" type="button" data-open="">Open drawer</button></j3w1-drawer>Install and register
Use the exact version. If npm publication is pending, install the release tarball from the installation guide.
npm install --save-exact @j3w1/ui@4.0.0import '@j3w1/ui/tokens.css';
import '@j3w1/ui/styles/drawer.css';
import '@j3w1/ui/register/drawer';Insert the maintained markup above after loading these imports. Its native children provide accessible names and form behavior; the empty custom tag alone does not create a control.
Copy the complete implementation
npx --package=@j3w1/ui@4.0.0 j3w1-ui copy drawer --out ./vendor/j3w1/drawerThe CLI verifies every file before writing and refuses overwrites. Serve the output over HTTP and open index.html. Keep its modules, CSS, tokens and licence notices together.
Public API
attributes
| Name | Type / signature | Meaning |
|---|---|---|
disabled | CustomEvent | Disable the native controls owned by this instance. |
loading | CustomEvent | Mark the primary control busy and prevent duplicate activation. |
properties
| Name | Type / signature | Meaning |
|---|---|---|
disabled | boolean | Delegates disabled state to native children. |
name | string | Native control name; application form keys remain explicit. |
open | boolean | Native modal state. |
returnValue | string | Native dialog result. Read only. |
methods
| Name | Type / signature | Meaning |
|---|---|---|
refresh | (): void | Reconnect behavior after replacing native child structure. |
focus | (options?: FocusOptions): void | Focus the first usable native control. |
show | (): void | Open using native showModal and remember the opener. |
close | (value?: string): void | Close with a result value. |
events
| Name | Type / signature | Meaning |
|---|---|---|
j3w1-open | CustomEvent | The modal opened. |
j3w1-close | CustomEvent | The modal closed and restored available opener focus. |
j3w1-cancel | CustomEvent | Native cancel request; prevent default to keep the modal open. |
j3w1-action | CustomEvent | An explicit dialog action; prevent default to keep it open. |
Dependencies and verification
Registered components: none. Style dependencies: button.
Package presence establishes an available implementation. Actual passes depend on the tested artifact and environment. Read specification execution evidence; read packed-consumer evidence; inspect the complete state matrix.
Full implementation manifest, API and token dependency closure
Canonical specification
Drawer drawer
A full-height modal panel anchored to the start or end edge; a native dialog on the overlay surface with a 1px overlay border on its inner edge.
Report an issue with this component
Tokens
Focus a role to inspect its values; follow its link for persistent usage and source details.
| Part | Role | Value | Use |
|---|---|---|---|
backdrop.bg | color.surface.backdropproposeduse-and-reportD-008 | rgb(0 0 0 / 65%) | Behind modal dialogs. |
root.bg | color.surface.overlayapproveduse | #1c1010 | D-041 surface.overlay: color.primitive.modern.overlay. |
root.border | color.border.overlayapproveduse | #c81a1a | D-041 owner correction: red control and floating-frame borders, distinct from rose text. |
root.ring | color.interaction.focus.ring-containerobserveduse | #ffa2a7 | Focus colour for containers and explicitly mapped filled controls (D-015); component rules determine ring geometry. |
root.z | z.drawerobserveduse | 20 | |
title.text | color.text.brightapproveduse | #ffa2a7 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
text.text | color.text.defaultapproveduse | #e99499 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
body.divider | color.border.dividerapproveduse | #482b2b | D-041 border.divider: color.primitive.modern.divider. |
item.text | color.text.defaultapproveduse | #e99499 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
item.bg-hover | color.interaction.hover.bg-strongapproveduse | #1c1010 | D-041 interaction.hover.bg-strong: color.primitive.modern.hover. |
item.text-hover | color.text.link-hoverobserveduse | #f4eeee | Link text on hover, over interaction.hover.bg-strong. |
close.text | color.action.tertiary.textapproveduse | #e99499 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
close.bg-hover | color.action.tertiary.hover-bgobserveduse | #1c0a09 | Ghost and icon button hover. |
close.ring | color.interaction.focus.ringobserveduse | #e53935 | 1px dashed focus ring on controls and rows. |
note.text | color.text.mutedapproveduse | #d4868b | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
State matrix
Every declared state and combination, statically rendered; the default cell is live. Sizes: compact, comfortable (switch with the density control above).
| State | Start edge | End edge |
|---|---|---|
default | ||
open | ||
closed | ||
focus-trapped | ||
reduced-motion |
Purpose
A modal dialog shaped as a full-height side panel, for navigation or a task. Below 900px the sidebar collapses into a drawer; above it, a drawer holds tasks that need more room than a dialog.
Anatomy
Parts as listed in the anatomy; the overlay border is the only visible edge.
States
| State | Visual | Non-colour channel |
|---|---|---|
| default | the panel open at its edge on the backdrop | open; the page behind is inert |
| open | as default; opacity 0 → 1 within 150 ms on capable hosts; never a slide | open; focus inside the panel |
| closed | panel and backdrop absent | no open; focus back on the opener |
| focus-trapped | while it holds focus, the panel carries the 2px solid color.interaction.focus.ring-container ring at −3px; the note says Tab cycles | Tab never leaves; the note text |
| reduced-motion | no opacity transition; present or absent | prefers-reduced-motion |
Items take the states of their list or navigation; Close takes the tertiary button’s hover and ring.
Keyboard
As in the key table. On open, focus goes to the first focusable element, the first item or Close.
Accessibility
Named by its title through aria-labelledby. Never re-implement the trap on
a non-modal element or a plain aside. The backdrop is decorative; a click on
it may close the drawer only because Close is also present. Logical
properties mirror the panel and its border in RTL. Text meets 4.5:1; the
border and ring 3:1.
Portability
Toolkits without a top layer draw the backdrop as a full-window overlay under the panel; toolkits with a native sidebar keep it non-modal and record that it is a sidebar, not a drawer.
Non-examples
A panel that slides in. A shadow or a rounded inner corner. A plain aside with a hand-written focus trap. A bottom sheet with a drag handle. No Close button. A drawer that stays open under a dialog. A translucent panel over page content.
Contrast report
15 declared pairs measured on resolved sRGB values with the WCAG 2.x formula; 0 waived (decorative only), 0 failing. A pass is a design target for the named role, not an application-wide conformance claim.
| Pair | State | Kind | Foreground | Background | Ratio | Min | Result |
|---|---|---|---|---|---|---|---|
| drawer default text | default | text | color.text.default #e99499 | color.surface.overlay #1c1010 | 8.10 | 4.5 | pass |
| drawer default border | default | ui | color.border.overlay #c81a1a | color.surface.overlay #1c1010 | 3.20 | 3 | pass |
| drawer open text | open | text | color.text.default #e99499 | color.surface.overlay #1c1010 | 8.10 | 4.5 | pass |
| drawer open border | open | ui | color.border.overlay #c81a1a | color.surface.overlay #1c1010 | 3.20 | 3 | pass |
| drawer focus-trapped text | focus-trapped | text | color.text.default #e99499 | color.surface.overlay #1c1010 | 8.10 | 4.5 | pass |
| drawer focus-trapped border | focus-trapped | ui | color.border.overlay #c81a1a | color.surface.overlay #1c1010 | 3.20 | 3 | pass |
| drawer focus-trapped outline | focus-trapped | ui | color.interaction.focus.ring-container #ffa2a7 | color.surface.overlay #1c1010 | 9.71 | 3 | pass |
| drawer reduced-motion text | reduced-motion | text | color.text.default #e99499 | color.surface.overlay #1c1010 | 8.10 | 4.5 | pass |
| drawer reduced-motion border | reduced-motion | ui | color.border.overlay #c81a1a | color.surface.overlay #1c1010 | 3.20 | 3 | pass |
| title on the overlay surface | — | text | color.text.bright #ffa2a7 | color.surface.overlay #1c1010 | 9.71 | 4.5 | pass |
| note on the overlay surface | — | text | color.text.muted #d4868b | color.surface.overlay #1c1010 | 6.70 | 4.5 | pass |
| close button on the overlay surface | — | text | color.action.tertiary.text #e99499 | color.surface.overlay #1c1010 | 8.10 | 4.5 | pass |
| ring on the overlay surface | — | ui | color.interaction.focus.ring #e53935 | color.surface.overlay #1c1010 | 4.39 | 3 | pass |
| hovered item | — | text | color.text.link-hover #f4eeee | color.interaction.hover.bg-strong #1c1010 | 16.19 | 4.5 | pass |
| row dividers (decorative) | — | ui | color.border.divider #482b2b | color.surface.overlay #1c1010 | 1.46 | 1 | pass |
Keys
| Key | Action |
|---|---|
| Tab / Shift+Tab | Cycles inside the drawer and never leaves it while open. |
| Escape | Closes the drawer and returns focus to the element that opened it. |
| Enter / Space | Activates the focused item or button; a navigation item closes the drawer after navigating. |
For agents
Component JSON ·Implementation brief ·Build a task kit ·
Related: dialog sidebar-nav menu toolbar
Stress fixtures: FX-LONG FX-320 FX-ZOOM-200 FX-I18N FX-RTL FX-RM FX-HC FX-OVERFLOW FX-STATE-MATRIX