J3W1 / DESIGN SYSTEM
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 · 1.1.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@1.1.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@1.1.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
Purpose
A panel that opens from the side to hold navigation or a task on narrow screens, or a secondary form on wide ones. It is a modal dialog with a different shape: full height, anchored to the start or end edge, with a single visible border on the edge that faces the page. Below 900px the sidebar collapses into a drawer; above it, a drawer holds tasks that need more room than a dialog.
Anatomy
The backdrop, color.surface.backdrop over the page; the panel on
color.surface.overlay with a 1px color.border.overlay on its inner edge;
the header with the title in color.text.bright and Close; the body, rows
divided by color.border.divider. The demonstration renders the panel inside
a stage of fixed height standing in for the viewport.
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 | nothing: the panel and the backdrop are absent | no open; focus back on the opener |
| focus-trapped | the panel carries the container ring, 2px solid color.interaction.focus.ring-container at −3px, when it holds focus; the note says Tab cycles | Tab never leaves; the note text |
| reduced-motion | no opacity transition; the panel is simply present or absent | prefers-reduced-motion |
Items inside the drawer take the states of the list or navigation they belong to; Close takes the tertiary button’s hover and ring.
Keyboard
Tab and Shift+Tab cycle inside the panel; the page behind is inert. Escape closes and returns focus to the opener. Enter or Space on a navigation item navigates and closes. On open, focus goes to the first focusable element, which is the first item or Close.
Accessibility
The panel is named by its title through aria-labelledby. showModal()
supplies the top layer and inertness; the theme never re-implements the trap
on a non-modal element or on a plain aside. The backdrop is decorative; a
click on it may close the drawer only because Close is also present. Both
edges use logical properties, so the panel and its border mirror in RTL with
no extra rules. Contrast: text 7.92:1 and title 9.49:1 on the overlay
surface, the border 4.30:1, the ring 4.30:1, hovered items 11.41:1 on the
strong hover fill.
Portability
A full-height box with one border and a dimmed backdrop. 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 from the edge. A drawer with a shadow or a rounded inner corner. A drawer that is a plain aside with a hand-written focus trap. A bottom sheet with a drag handle. A drawer with no Close button. A drawer that stays open under a dialog. A translucent panel over page content.