Persist
stimeo--persist
Saves form values to localStorage and restores them across reloads and Turbo visits.
The stimeo--persist controller restores any saved values under key (falling back to the element's id) on connect, then debounce-saves on every input/change. It marks the form data-persist-restored when it restores, and dispatches stimeo--persist:restore / :save / :clear. Password fields — and anything listed in exclude — are never written. A clear() action, or the clearOn event (e.g. submit), drops the draft. Restoring never moves focus. Behavior only — state lives entirely in localStorage (no module-scope state), so multiple instances never interfere. The input listener and debounce timer are removed on disconnect (Turbo navigation included), where a pending save is flushed so an in-flight edit is not lost.
<%# Persist demo: type into the fields and the draft is saved to localStorage
(debounced). Reload or navigate away and back — the values are restored and the
"draft restored" note appears (data-persist-restored). "Clear draft" calls the
clear() action. The library owns the storage and the data hook; this demo only
styles the restored note. %>
<div class="persist-demo">
<form
class="persist"
data-controller="stimeo--persist"
data-stimeo--persist-key-value="stimeo-demo-contact">
<p class="persist__restored"><%= t("components.persist.demo.restored") %></p>
<label class="persist__field">
<span><%= t("components.persist.demo.name") %></span>
<input type="text" name="name">
</label>
<label class="persist__field">
<span><%= t("components.persist.demo.message") %></span>
<textarea name="message" rows="3"></textarea>
</label>
<button type="button" class="demo-trigger" data-action="stimeo--persist#clear">
<%= t("components.persist.demo.clear") %>
</button>
</form>
</div>
/*
* Presentation-only styles for the persist demo.
* The library toggles data-persist-restored on the form; this CSS reveals the
* "draft restored" note while that hook is present.
*/
.persist-demo {
max-width: 28rem;
}
.persist {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.persist__field {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.persist__field input,
.persist__field textarea {
padding: 0.5rem;
border: 1px solid var(--border);
border-radius: 0.375rem;
font: inherit;
resize: vertical;
}
/* The note appears only after a draft has been restored. */
.persist__restored {
display: none;
margin: 0;
font-size: 0.85rem;
color: var(--accent);
}
.persist[data-persist-restored] .persist__restored {
display: block;
}
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--persist"
Targets
| Name | Description | Attribute |
|---|---|---|
field
|
Optional fields to limit persistence to; defaults to the form's named controls. | data-stimeo--persist-target="field" |
Values
| Name | Description | Attribute |
|---|---|---|
key
|
localStorage namespace; falls back to the element id. Disabled when neither is set. |
data-stimeo--persist-key-value |
debounce
|
Debounce in milliseconds before a save (default 400). | data-stimeo--persist-debounce-value |
exclude
|
Field names/types never saved (default ["password"]). |
data-stimeo--persist-exclude-value |
clearOn
|
Event name that clears the draft (e.g. submit); empty disables it. |
data-stimeo--persist-clear-on-value |
Actions
| Name | Action |
|---|---|
clear
|
stimeo--persist#clear |
Events
| Name | Description | Event |
|---|---|---|
restore
|
Fires when values are restored, with detail.key. |
stimeo--persist:restore |
save
|
Fires after a debounced save, with detail.key. |
stimeo--persist:save |
clear
|
Fires when the draft is cleared, with detail.key. |
stimeo--persist:clear |
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-persist-restored |
Form (root) | Present when values were restored from a saved draft. |