Highlight on Insert
stimeo--highlight
Briefly flags a freshly inserted element so CSS can flash / fade it in, then clears it.
The stimeo--highlight controller flags a freshly inserted element with data-highlight so CSS can flash or fade it in, then removes the flag after duration ms — replacing hand-rolled setTimeout bookkeeping. In the default (self) mode the controller element is highlighted once on connect; with observe it switches to container mode, where a MutationObserver highlights every element child added to it (e.g. Turbo Stream appends). Each highlight dispatches stimeo--highlight:start and, on removal, stimeo--highlight:end, both carrying detail.element. Behavior only — it ships no color or animation, just the hook (pair with CSS, and with Announcer / Flash for non-visual notice). Under prefers-reduced-motion the emphasis is suppressed entirely (the element simply appears). The self hook is removed before its duration elapses so it never lingers into a Turbo cache snapshot, and the observer and pending timers are torn down on disconnect (Turbo navigation included).
- Item 1
- Item 2
Each new row is briefly highlighted, then fades back. The container runs in observe mode.
<%# Highlight-on-insert demo: the list runs in container (observe) mode, so the
controller highlights each <li> added to it. This catalog has no Turbo Stream
backend, so demo.js appends rows on click — exactly the mutation a Turbo Stream
append would make. The library only toggles data-highlight; demo.css owns the
flash/fade transition keyed on that hook. %>
<div class="highlight-demo">
<button
type="button"
class="demo-trigger"
data-highlight-add
data-item-label="<%= t("components.highlight.demo.item") %>">
<%= t("components.highlight.demo.add") %>
</button>
<ul
class="highlight-demo__list"
data-controller="stimeo--highlight"
data-stimeo--highlight-observe-value="true">
<li><%= t("components.highlight.demo.item") %> 1</li>
<li><%= t("components.highlight.demo.item") %> 2</li>
</ul>
<p class="highlight-demo__note"><%= t("components.highlight.demo.note") %></p>
</div>
/*
* Presentation-only styles for the highlight demo. The library only toggles
* data-highlight on each inserted row; this CSS owns the flash/fade: rows transition
* their background back to normal, and a highlighted row briefly shows the accent.
* Under reduced motion the controller never sets the hook, so no transition runs.
*/
.highlight-demo {
display: flex;
flex-direction: column;
gap: 0.75rem;
max-width: 28rem;
}
.highlight-demo__list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.highlight-demo__list li {
padding: 0.5rem 0.75rem;
border: 1px solid var(--border);
border-radius: 0.375rem;
background: var(--surface-card);
transition: background-color 1s ease-out;
}
.highlight-demo__list li[data-highlight] {
background: var(--amber-50);
transition: none;
}
.highlight-demo__note {
margin: 0;
color: var(--color-text-muted);
}
// Highlight-on-insert demo (consumer-side JS).
//
// The list runs in container (observe) mode, so the controller highlights each <li>
// it gains on its own. This catalog has no Turbo Stream backend, so the button appends
// rows — the same mutation a Turbo Stream append makes — and the controller reacts.
document.querySelectorAll(".highlight-demo").forEach((root) => {
const list = root.querySelector(".highlight-demo__list");
const label = root.querySelector("[data-highlight-add]")?.dataset.itemLabel;
if (!list) return;
// Idempotent: Turbo can re-run this inline module on navigation; wire each root once
// so a click never appends two rows.
if (root.dataset.demoWired) return;
root.dataset.demoWired = "1";
let count = list.children.length;
root.querySelector("[data-highlight-add]")?.addEventListener("click", () => {
const li = document.createElement("li");
li.textContent = `${label ?? "Item"} ${(count += 1)}`;
list.appendChild(li);
});
});
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--highlight"
Values
| Name | Description | Attribute |
|---|---|---|
duration
|
Milliseconds the data-highlight hook stays applied (default 1500). |
data-stimeo--highlight-duration-value |
observe
|
Container mode: watch for added element children and highlight each (default false). |
data-stimeo--highlight-observe-value |
Events
| Name | Description | Event |
|---|---|---|
start
|
Fires when an element starts being highlighted, with detail.element. |
stimeo--highlight:start |
end
|
Fires when the highlight is removed, with detail.element. |
stimeo--highlight:end |
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-highlight |
The highlighted element (self or an added child) | Present (set to true) while highlighted; removed after duration. |