Dismissible
stimeo--dismissible
Closes a banner or notice while keeping focus from being orphaned.
The stimeo--dismissible controller removes (or, in hide mode, hides) an element on a close button — or on Escape when closeOnEscape is set. Its accessibility job is focus safety: when focus is inside the element being removed, it is moved first to the fallback target, then the next focusable element, then the previous one, then document.body, so the vanishing close button never strands the user. A dismiss event carries the mode. Behavior only — exit transitions and semantics like role="alert" are owned by the consumer.
Keyboard
| Key | Action |
|---|---|
| Enter / Space | Activate the close button (native button). |
| Escape | Dismiss when closeOnEscape is set and focus is inside. |
<%# Markup for the dismissible (element dismissed via a close button) demo.
The close button (or Escape) removes the element, and if focus was inside before
removal it is moved to a safe place (fallback → next/previous focusable → body).
Here it uses hide mode (adds hidden) and allows closing with Escape too.
Semantics like role="status" are the consumer markup's responsibility. %>
<div
class="dismissible"
data-controller="stimeo--dismissible"
data-stimeo--dismissible-mode-value="hide"
data-stimeo--dismissible-close-on-escape-value="true">
<div
class="dismissible__banner"
role="status"
data-stimeo--dismissible-target="root">
<span class="dismissible__icon" aria-hidden="true">✓</span>
<p class="dismissible__message"><%= t("components.dismissible.demo.message") %></p>
<button
class="dismissible__close"
type="button"
aria-label="<%= t('components.dismissible.demo.close') %>"
data-action="click->stimeo--dismissible#dismiss">
<span aria-hidden="true">×</span>
</button>
</div>
<a class="dismissible__after" href="#" data-stimeo--dismissible-target="fallback">
<%= t("components.dismissible.demo.fallback") %>
</a>
</div>
/*
* Presentation-only styles for the dismissible demo.
* Dismissal is expressed via hide mode (the library adds hidden to the root).
*/
.dismissible {
display: flex;
flex-direction: column;
gap: 0.5rem;
max-width: 32rem;
}
.dismissible__banner {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
border: 1px solid var(--leaf-50);
border-radius: 0.5rem;
background: var(--leaf-50);
color: var(--leaf-500);
}
.dismissible__icon {
font-weight: 700;
}
.dismissible__message {
margin: 0;
flex: 1;
}
.dismissible__close {
display: grid;
place-items: center;
width: 1.75rem;
height: 1.75rem;
border: 0;
border-radius: 0.25rem;
background: transparent;
color: inherit;
font-size: 1.25rem;
line-height: 1;
cursor: pointer;
}
.dismissible__close:hover {
background: var(--leaf-50);
}
.dismissible__after {
align-self: flex-start;
font-size: 0.875rem;
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--dismissible"
Targets
| Name | Description | Attribute |
|---|---|---|
root
|
The element to dismiss; falls back to the host element when omitted. | data-stimeo--dismissible-target="root" |
fallback
|
The preferred element to receive focus when dismissal would orphan focus inside root. | data-stimeo--dismissible-target="fallback" |
Values
| Name | Description | Attribute |
|---|---|---|
mode
|
How to dismiss: remove removes the element from the DOM (default) or hide sets it hidden. |
data-stimeo--dismissible-mode-value |
closeOnEscape
|
When true, pressing Escape while focus is inside dismisses the element (default false). | data-stimeo--dismissible-close-on-escape-value |
Actions
| Name | Description | Action |
|---|---|---|
dismiss
|
Retreats focus if needed, then removes or hides the element per mode. |
stimeo--dismissible#dismiss |
Events
| Name | Description | Event |
|---|---|---|
dismiss
|
Dispatched synchronously just before removal/hiding, with the resolved { mode } in detail. |
stimeo--dismissible:dismiss |
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 |
Root | Added on dismiss in hide mode (remove mode deletes the node). |
data-state |
Root | "open" / "closing" — optional exit-animation starting point. |