Dialog
stimeo--dialog
An accessible modal dialog. Traps focus, locks background scroll, and closes on Esc.
The stimeo--dialog controller implements the WAI-ARIA modal dialog pattern. Opening moves focus into the dialog and traps it there with Tab; the background scroll is locked while open. Esc closes the dialog and focus returns to the trigger. Clicking the backdrop also closes it. The library provides behavior only — styling is owned by this Playground.
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="dialog__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" data-action="click->stimeo--dialog#close"><%= t(
"components.dialog.demo.cancel"
) %></button>
<button type="button" data-action="click->stimeo--dialog#close"><%= t(
"components.dialog.demo.ok"
) %></button>
</div>
</div>
</div>
</div>
/*
* Presentation-only styles for the dialog demo.
* Stimeo UI itself ships behavior only (no CSS), so the styling lives here.
*/
.dialog__trigger {
padding: 0.5rem 1rem;
font-size: 1rem;
cursor: pointer;
border: 1px solid var(--border-interactive);
border-radius: 0.375rem;
background: var(--surface-card);
}
/* 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;
}
.dialog__actions button {
padding: 0.5rem 1rem;
font-size: 1rem;
cursor: pointer;
border: 1px solid var(--border);
border-radius: 0.375rem;
background: var(--surface-card);
}
.dialog__actions button:last-child {
color: var(--white);
background: var(--accent);
border-color: var(--accent);
}
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. |