Transition
stimeo--transition
The groundwork for enter and leave animations, hiding only once the movement has finished.
The groundwork under showing and hiding with animation. On the way in it makes the element visible, applies the starting state, and moves to the end state on the next frame, so the CSS transition really runs. On the way out it does the reverse and hides the element only once the movement finishes. The end is detected from the browser's own signal, with a timer as insurance when that never arrives. A direction can be reversed part-way. Which stage it is in is readable from CSS. Visitors who ask for less motion get the change immediately. The animation itself is your CSS; this decides when to switch.
Keyboard
This component has no keyboard interactions of its own.
<%# Transition demo: the panel is the controller element; enter/leave class values point
at the demo.css classes that define the timing and start/end states. The toggle button
lives outside the (hideable) panel, so it invokes the controller's toggle action through
the playground's exposed Stimulus app (window.Stimulus) in demo.js. The library only
stages the classes and reflects data-transition-state / hidden; demo.css owns the look. %>
<div class="transition-demo">
<button
type="button"
class="demo-trigger"
data-transition-demo-toggle
aria-expanded="false"
aria-controls="transition-demo-panel">
<%= t("components.transition.demo.toggle") %>
</button>
<div
id="transition-demo-panel"
class="transition-demo__panel"
data-controller="stimeo--transition"
data-stimeo--transition-enter-value="te-enter"
data-stimeo--transition-enter-from-value="te-from"
data-stimeo--transition-enter-to-value="te-to"
data-stimeo--transition-leave-value="te-leave"
data-stimeo--transition-leave-from-value="te-to"
data-stimeo--transition-leave-to-value="te-from"
hidden>
<%= t("components.transition.demo.panel") %>
</div>
</div>
/*
* Presentation-only styles for the transition demo. The library stages the te-* classes
* (named by the controller's enter/leave values) and toggles hidden; these classes define
* the timing and the start/end states. After a transition completes the controller strips
* the stage classes, so the panel rests at the te-to (visible) state by default.
*/
.transition-demo {
display: flex;
flex-direction: column;
gap: 0.75rem;
align-items: flex-start;
}
.transition-demo__panel {
max-width: 22rem;
padding: 1rem;
border: 1px solid var(--border);
border-radius: 0.5rem;
background: var(--surface-subtle);
}
/* Stage classes referenced by the controller's enter/leave value attributes. */
.te-enter {
transition:
opacity 0.3s ease-out,
transform 0.3s ease-out;
}
.te-leave {
transition:
opacity 0.2s ease-in,
transform 0.2s ease-in;
}
.te-from {
opacity: 0;
transform: translateY(-0.5rem);
}
.te-to {
opacity: 1;
transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
.te-enter,
.te-leave {
transition: none;
}
}
// Transition demo (consumer-side JS).
//
// The toggle button sits outside the hideable panel, so a plain data-action can't reach
// the panel's controller. The playground exposes its Stimulus application as
// window.Stimulus, so we fetch the controller instance and call its toggle() action — and
// mirror the open state onto aria-expanded from the controller's entered/left events.
document.querySelectorAll(".transition-demo").forEach((root) => {
const panel = root.querySelector('[data-controller~="stimeo--transition"]');
const button = root.querySelector("[data-transition-demo-toggle]");
if (!panel || !button) return;
// Idempotent: Turbo can re-run this inline module on navigation; wire each root once so a
// single click does not toggle twice (which flashes the panel open then shut). The marker
// is a property, not an attribute: Turbo copies attributes into its page snapshot, so an
// attribute one comes back set on a restored page whose elements carry no listeners.
if (root.demoWired) return;
root.demoWired = true;
button.addEventListener("click", () => {
window.Stimulus?.getControllerForElementAndIdentifier(panel, "stimeo--transition")?.toggle();
});
panel.addEventListener("stimeo--transition:entered", () => {
button.setAttribute("aria-expanded", "true");
});
panel.addEventListener("stimeo--transition:left", () => {
button.setAttribute("aria-expanded", "false");
});
});
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--transition"
Values
| Name | Description | Attribute |
|---|---|---|
enter
|
Classes applied for the whole enter transition (e.g. timing / easing). | data-stimeo--transition-enter-value |
enterFrom
|
Enter start-state classes (applied first, removed next frame). | data-stimeo--transition-enter-from-value |
enterTo
|
Enter end-state classes (applied next frame so the transition runs). | data-stimeo--transition-enter-to-value |
leave
|
Classes applied for the whole leave transition. | data-stimeo--transition-leave-value |
leaveFrom
|
Leave start-state classes. | data-stimeo--transition-leave-from-value |
leaveTo
|
Leave end-state classes. | data-stimeo--transition-leave-to-value |
timeout
|
Safety completion timeout in ms (0 = auto from the computed duration). | data-stimeo--transition-timeout-value |
Actions
| Name | Action |
|---|---|
enter
|
stimeo--transition#enter |
leave
|
stimeo--transition#leave |
toggle
|
stimeo--transition#toggle |
Events
| Name | Description | Event |
|---|---|---|
entered
|
Fires when the enter transition completes. | stimeo--transition:entered |
left
|
Fires when the leave transition completes. | stimeo--transition:left |
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-transition-state |
Controller element | entering / entered / leaving / left. |
hidden |
Controller element | Removed when entering starts; re-applied when leaving completes. |