Reset on Restore
stimeo--reset-on-restore
Puts a page you come back to the way it was written, so going back never lands mid-interaction.
Turbo keeps a copy of each page so it can show it again instantly when you go back. That copy also holds the open menu, the half-typed value and the notice you never dismissed, so the page you return to looks frozen part-way through something. When such a copy comes back, this puts that transient state the way the page was written. A page that is still on screen is never touched, so nothing you are typing disappears. What to put back is declared in your markup: remove these attributes, remove these classes, reset this form, put this one field back the way it was written, hide this again, remove this. One of these on the page covers everything within its scope.
Open the details, type a value and show the toast, then simulate a restore. Leaving this page and coming back with the Back button does the same.
Open me
This panel is open. On a restored page it is closed again.
Keyboard
This component has no keyboard interactions of its own.
<%# Markup for the restore reset demo.
When Turbo restores a page from its cache (the Back button), the controller returns the
transient UI it covers to the way the page was written. A page still on screen is never
reset. "Simulate a restore" stands in for the Back button: demo.js puts a copy of the
covered region in its place, as Turbo does with a cached page, and the copy is reset as
it connects. Open the details, type in the field, and show the toast first. %>
<div class="reset-demo">
<p class="reset-demo__note"><%= t("components.reset_on_restore.demo.note") %></p>
<div class="reset-demo__page" data-reset-demo="page" data-controller="stimeo--reset-on-restore">
<details class="reset-demo__details" data-reset-attr="open">
<summary><%= t("components.reset_on_restore.demo.details_summary") %></summary>
<p><%= t("components.reset_on_restore.demo.details_body") %></p>
</details>
<label class="reset-demo__field">
<span><%= t("components.reset_on_restore.demo.input_label") %></span>
<input type="text" class="demo-input" data-reset-value
placeholder="<%= t("components.reset_on_restore.demo.input_placeholder") %>">
</label>
<div class="reset-demo__toast" data-reset-demo="toast" data-reset-hidden hidden role="status">
<%= t("components.reset_on_restore.demo.toast_text") %>
</div>
</div>
<div class="reset-demo__controls">
<button type="button" class="demo-trigger" data-reset-demo="show-toast">
<%= t("components.reset_on_restore.demo.show_toast") %>
</button>
<button type="button" class="demo-trigger" data-reset-demo="simulate">
<%= t("components.reset_on_restore.demo.simulate") %>
</button>
</div>
<p class="reset-demo__status" data-reset-demo="status" aria-live="polite"
data-done-text="<%= t("components.reset_on_restore.demo.done") %>"></p>
</div>
/*
* Presentation-only styles for the restore reset demo.
* The library only mutates the marked elements of a restored page (removing
* attributes, restoring fields to their authored state, re-hiding, removing
* nodes); this CSS owns layout.
*/
.reset-demo {
display: flex;
flex-direction: column;
gap: 0.75rem;
max-width: 32rem;
}
.reset-demo__note {
margin: 0;
font-size: 0.85rem;
color: var(--color-text-muted);
}
.reset-demo__page {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.reset-demo__status {
margin: 0;
min-height: 1.25rem;
font-size: 0.85rem;
color: var(--color-text-muted);
}
.reset-demo__details {
padding: 0.6rem 0.8rem;
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.reset-demo__field {
display: flex;
flex-direction: column;
gap: 0.25rem;
font-size: 0.85rem;
}
.reset-demo__toast {
padding: 0.5rem 0.75rem;
border: 1px solid var(--leaf-500);
border-radius: 0.5rem;
background: var(--leaf-50);
color: var(--leaf-700);
font-size: 0.9rem;
}
.reset-demo__toast[hidden] {
display: none;
}
.reset-demo__controls {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
// Consumer-side JS for the restore reset demo (demo-only).
// The Back button restores a page from Turbo's cache: a copy of the page taken earlier,
// which a new controller instance connects to. "Simulate a restore" does the same to the
// covered region — it puts a copy of it in its place — so the copy is reset as it connects.
// The controls sit outside the copied region, so their listeners survive the swap.
const demo = document.querySelector(".reset-demo");
const showToast = document.querySelector("[data-reset-demo='show-toast']");
const simulate = document.querySelector("[data-reset-demo='simulate']");
const status = document.querySelector("[data-reset-demo='status']");
if (showToast) {
showToast.addEventListener("click", () => {
const toast = document.querySelector("[data-reset-demo='toast']");
if (toast) toast.hidden = false;
});
}
if (simulate) {
simulate.addEventListener("click", () => {
if (status) status.textContent = "";
const page = document.querySelector("[data-reset-demo='page']");
if (page) page.replaceWith(page.cloneNode(true));
});
}
// The controller reports a reset once a restored copy has been put back.
if (demo && status) {
demo.addEventListener("stimeo--reset-on-restore:reset", () => {
status.textContent = status.dataset.doneText || "";
});
}
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--reset-on-restore"
Values
| Name | Description | Attribute |
|---|---|---|
scope
|
Selector narrowing the scan to a descendant; empty scans the whole subtree. | data-stimeo--reset-on-restore-scope-value |
Actions
| Name | Description | Action |
|---|---|---|
reset
|
Runs the reset sweep now (a restored page runs it on its own as it connects). | stimeo--reset-on-restore#reset |
Events
| Name | Description | Event |
|---|---|---|
reset
|
Fires after the reset sweep runs: on a restored page as it connects, and on a manual run. | stimeo--reset-on-restore:reset |