Dialog
stimeo--dialog
An accessible modal dialog. Keeps focus inside, stops background scrolling, closes on Esc.
Implements the WAI-ARIA modal dialog pattern. Opening moves focus into the dialog and keeps it there as you Tab. Background scrolling stops while it is open. Esc closes it and focus returns to the button that opened it. Clicking the backdrop closes it too.
Confirm action
Are you sure you want to continue?
Keyboard
| Key | Action |
|---|---|
| Tab / Shift+Tab | Cycle focus within the dialog (focus trap). |
| Esc | Close the dialog and return focus to the trigger. |
<%# Markup for the dialog demo.
The single source used for both the live render and the copy-paste code.
stimeo--dialog provides the focus trap, Esc, and background scroll lock.
The behavior is the library's; the look (classes) is the Playground's CSS. %>
<div class="dialog" data-controller="stimeo--dialog">
<button
type="button"
class="demo-trigger"
data-stimeo--dialog-target="trigger"
data-action="click->stimeo--dialog#open">
<%= t("components.dialog.demo.open_button") %>
</button>
<%# The role="dialog" + aria-modal region. Starts hidden (closed).
Clicking this element itself (the backdrop) also closes it. %>
<div
class="dialog__backdrop"
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
data-stimeo--dialog-target="dialog"
data-action="click->stimeo--dialog#closeOnBackdrop"
hidden>
<div class="dialog__panel">
<h3 id="dialog-title" class="dialog__title"><%= t(
"components.dialog.demo.confirm_title"
) %></h3>
<p><%= t("components.dialog.demo.confirm_desc") %></p>
<div class="dialog__actions">
<button
type="button"
class="demo-trigger"
data-action="click->stimeo--dialog#close"><%= t(
"components.dialog.demo.cancel"
) %></button>
<button
type="button"
class="demo-trigger demo-trigger--primary"
data-action="click->stimeo--dialog#close"><%= t(
"components.dialog.demo.ok"
) %></button>
</div>
</div>
</div>
</div>
/* Use the role="dialog" region as a centered backdrop. */
.dialog__backdrop {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
background: rgba(0, 0, 0, 0.5);
z-index: 50;
}
/* Setting display explicitly overrides the hidden attribute's default display:none, so
re-declare display:none while closed to honor the library's hidden control. */
.dialog__backdrop[hidden] {
display: none;
}
.dialog__panel {
width: min(28rem, 100%);
padding: 1.5rem;
background: var(--bg);
border-radius: 0.5rem;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
}
.dialog__title {
margin: 0 0 0.5rem;
}
.dialog__actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
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--dialog"
Targets
| Name | Description | Attribute |
|---|---|---|
trigger
required
|
The button that opens the dialog; focus returns here on close. | data-stimeo--dialog-target="trigger" |
dialog
required
|
The dialog element (role=dialog). Clicking it outside its content acts as the backdrop and closes. |
data-stimeo--dialog-target="dialog" |
Actions
| Name | Description | Action |
|---|---|---|
close
|
Closes the dialog and returns focus to the trigger. | stimeo--dialog#close |
closeOnBackdrop
|
Closes when the backdrop (the dialog element outside its content) is clicked. Wire it on the dialog element. | stimeo--dialog#closeOnBackdrop |
open
|
Opens the dialog, moves focus inside, traps Tab, and locks background scroll. | stimeo--dialog#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 |
|---|---|---|
hidden |
Dialog element | Present when closed, removed when open — toggles visibility. |
aria-modal="true" |
Dialog element | Marks the dialog as modal for assistive technology. |
inert |
Background siblings | Applied to sibling elements while open so they cannot be interacted with. |