Drawer
stimeo--drawer
A modal slide-over panel from a screen edge: data-state sync and deferred hidden.
The stimeo--drawer controller implements the WAI-ARIA Dialog (Modal) pattern for a slide-over panel. It is the same modal as the dialog (focus trap, scroll lock, background inert, focus restore) plus the state plumbing a slide needs: data-state (open/closed) is synced on the panel and overlay so CSS can animate, and hidden is applied only after the close transition finishes so the exit animation can play. The placement value is reflected as data-placement (left/right/top/bottom) — the controller never computes coordinates. Escape and an overlay (backdrop) click close it; the edge, slide direction, and easing are your CSS.
Keyboard
| Key | Action |
|---|---|
| Esc | Close the drawer and return focus to the opener. |
| Tab / Shift+Tab | Cycle focus within the panel (focus trap). |
<%# Markup for the drawer (side panel) demo.
A modal panel that slides in from the right edge. In addition to focus move/trap,
Esc, background scroll lock, background inert, and returning focus to the trigger,
the library syncs data-state (open/closed) and applies hidden once the transition
finishes. Which edge it moves from and how (the slide) and the interpretation of
placement are expressed by the consumer's CSS (demo.css) reading data-placement /
data-state. %>
<div
class="drawer"
data-controller="stimeo--drawer"
data-stimeo--drawer-placement-value="right">
<button
type="button"
class="drawer__trigger"
data-stimeo--drawer-target="trigger"
data-action="click->stimeo--drawer#open">
<%= t("components.drawer.demo.open_button") %>
</button>
<%# Overlay (backdrop). Click to close. data-state stays in sync with the panel. %>
<div
class="drawer__overlay"
data-stimeo--drawer-target="overlay"
data-action="click->stimeo--drawer#closeOnBackdrop"
hidden>
<%# The role="dialog" + aria-modal panel. Starts closed / hidden. %>
<div
class="drawer__panel"
role="dialog"
aria-modal="true"
aria-labelledby="drawer-title"
data-stimeo--drawer-target="panel"
data-state="closed"
hidden>
<h3 id="drawer-title" class="drawer__title">
<%= t("components.drawer.demo.title") %>
</h3>
<p><%= t("components.drawer.demo.body") %></p>
<button type="button" class="drawer__close" data-action="click->stimeo--drawer#close">
<%= t("components.drawer.demo.close") %>
</button>
</div>
</div>
</div>
/*
* Presentation-only styles for the drawer demo.
* Stimeo UI itself ships behavior only (no CSS). Slide direction, distance, and the
* animation curve are the consumer's responsibility, expressed by reading the
* data-state (open/closed) and data-placement (left/right/top/bottom) the library syncs.
*/
.drawer__trigger,
.drawer__close {
padding: 0.5rem 1rem;
font: inherit;
cursor: pointer;
border: 1px solid var(--border-interactive);
border-radius: 0.375rem;
background: var(--surface-card);
}
/* Overlay: a full-screen semi-transparent backdrop that fades in/out. */
.drawer__overlay {
position: fixed;
inset: 0;
background: rgb(0 0 0 / 0.5);
opacity: 1;
transition: opacity 0.2s ease;
z-index: 50;
}
.drawer__overlay[data-state="closed"] {
opacity: 0;
}
/* Keep it fully removed while hidden is set (honor the library's hidden control). */
.drawer__overlay[hidden] {
display: none;
}
/* Panel: fixed to the right edge; data-state drives the slide transition. */
.drawer__panel {
position: fixed;
top: 0;
bottom: 0;
width: min(20rem, 80vw);
padding: 1.5rem;
background: var(--surface-card);
box-shadow: -8px 0 30px rgb(15 23 42 / 0.2);
transition: transform 0.25s ease;
overflow: auto;
}
.drawer__panel[hidden] {
display: none;
}
/* placement=right: enters/exits from the right edge; parks off-screen when closed. */
.drawer__panel[data-placement="right"] {
right: 0;
transform: translateX(0);
}
.drawer__panel[data-placement="right"][data-state="closed"] {
transform: translateX(100%);
}
.drawer__title {
margin: 0 0 0.5rem;
font-size: 1.125rem;
}
.drawer__close {
margin-top: 1.5rem;
}
This demo needs no consumer-side JS (the controller handles the behavior).
These demo styles use shared design tokens (light + dark). Copy the shared styles too, then toggle data-theme on your root element for dark mode.
The data-* attributes you add to your own HTML to wire this component. Put the data-controller below on a root element, then place its targets / values / actions inside that element.
On the root element
data-controller="stimeo--drawer"
Targets
| Name | Description | Attribute |
|---|---|---|
trigger
|
The button that opens the drawer; focus falls back here on close. | data-stimeo--drawer-target="trigger" |
overlay
|
The backdrop wrapping the panel; syncs data-state and closes the drawer when clicked directly. |
data-stimeo--drawer-target="overlay" |
panel
required
|
The role="dialog" aria-modal panel that slides in; carries data-state/data-placement and is hidden only after the exit transition. |
data-stimeo--drawer-target="panel" |
Values
| Name | Description | Attribute |
|---|---|---|
placement
|
The edge the drawer slides from, reflected as data-placement for CSS (default right). |
data-stimeo--drawer-placement-value |
open
|
Whether the drawer is open; reflects/initializes the open state (default false). | data-stimeo--drawer-open-value |
Actions
| Name | Description | Action |
|---|---|---|
close
|
Sets data-state to closed to start the exit slide, then defers hidden and modal teardown until the transition ends. |
stimeo--drawer#close |
closeOnBackdrop
|
Closes only when the overlay itself (not its contents) is clicked. | stimeo--drawer#closeOnBackdrop |
open
|
Reveals the panel/overlay, flips data-state to open to play the enter slide, traps focus, and locks background scroll. |
stimeo--drawer#open |
State hooks
The library only manages these ARIA/data attributes and custom properties. Your CSS reads them to render the look — selectors like [aria-selected], [aria-expanded], or var(--stimeo--…) hook into this state.
| Hook | Target | Meaning |
|---|---|---|
data-state |
Panel / Overlay | open / closed; the start point for the slide transition. |
hidden |
Panel / Overlay | Applied after the close transition completes (fully hidden). |
data-placement |
Panel | left / right / top / bottom; a CSS flag for which edge to slide from. |
inert |
Background siblings | Added to elements outside the panel while open. |