Scroll Visibility
stimeo--scroll-visibility
Shows and hides an element by how far or which way you scroll, for back-to-top or a hiding header.
Shows and hides an element as you scroll, in one of two ways: appearing once you are past a certain distance, or hiding while you scroll down and returning when you scroll up. That suits a back-to-top button and a header that gets out of the way while reading. It watches the page by default, and in a layout where the page itself does not move you can point it at the box that does. While hidden, the element also leaves the Tab order, so focus never lands on a button nobody can see. Returning to the top is instant for visitors who ask for less motion.
Scroll down inside this box and a back-to-top button appears at the bottom right.
Content line 1
Content line 2
Content line 3
Content line 4
Content line 5
Content line 6
Content line 7
Content line 8
Content line 9
Content line 10
Content line 11
Content line 12
Keyboard
| Key | Action |
|---|---|
| Enter / Space | Activate the back-to-top button (standard button behavior). |
<%# Markup for the scroll-visibility (scroll-driven visibility toggle / GoToTop) demo.
The library toggles the target via hidden / data-state once the scroll source passes a
threshold, and toTop scrolls that source back to the top. Here the scroll source is the
demo's own scroll box (root points at it), so the demo is self-contained and works even
when the page itself does not scroll on the window. The look and transition are demo.css's.
Scroll inside the box and a "Back to top" button appears at the bottom-right. %>
<div
class="scroll-visibility-demo"
data-controller="stimeo--scroll-visibility"
data-stimeo--scroll-visibility-root-value="#scroll-visibility-demo-scroller"
data-stimeo--scroll-visibility-offset-value="120"
data-stimeo--scroll-visibility-mode-value="offset">
<div id="scroll-visibility-demo-scroller" class="scroll-visibility-demo__scroller"
tabindex="0" role="region" aria-labelledby="scroll-visibility-demo-hint">
<p id="scroll-visibility-demo-hint" class="scroll-visibility-demo__hint"><%= t(
"components.scroll_visibility.demo.hint"
) %></p>
<% 12.times do |i| %>
<p class="scroll-visibility-demo__line"><%= t(
"components.scroll_visibility.demo.line", number: i + 1
) %></p>
<% end %>
</div>
<button
type="button"
class="go-to-top"
hidden
data-stimeo--scroll-visibility-target="element"
data-action="click->stimeo--scroll-visibility#toTop">
<%= t("components.scroll_visibility.demo.to_top") %>
</button>
</div>
/* Presentation CSS for scroll-visibility. The library only toggles hidden / data-state
and does the toTop scroll, so visuals like the scroll box, positioning, and fade are
drawn here. The demo scrolls inside its own box (the controller's root) rather than the
window, so the "Back to top" button is pinned to the box, not the viewport. */
.scroll-visibility-demo {
position: relative; /* positioning context for the absolutely-pinned button */
color: var(--color-text-muted);
}
/* The scroll source the controller observes (data-...-root-value points here). */
.scroll-visibility-demo__scroller {
max-height: 16rem;
overflow-y: auto;
padding: 1rem;
border: 1px solid var(--border-default);
border-radius: 0.5rem;
background: var(--surface-subtle);
}
.scroll-visibility-demo__scroller:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.scroll-visibility-demo__hint {
margin: 0 0 0.5rem;
font-weight: 600;
}
.scroll-visibility-demo__line {
margin: 0.5rem 0;
}
/* "Back to top" button, pinned to the demo box. Visible only once the library removes
hidden (and the control is back in the focus order). */
.go-to-top {
position: absolute;
right: 1.25rem;
bottom: 1.25rem;
z-index: 1;
padding: 0.6rem 1rem;
border: none;
border-radius: 999px;
background: var(--color-primary-hover);
color: var(--white);
font-weight: 600;
cursor: pointer;
box-shadow: 0 6px 16px rgba(var(--vital-rgb), 0.4);
transition: opacity 0.2s ease, transform 0.2s ease;
}
.go-to-top:hover {
transform: translateY(-2px);
}
.go-to-top:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
.go-to-top {
transition: none;
}
.go-to-top:hover {
transform: none;
}
}
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--scroll-visibility"
Targets
| Name | Description | Attribute |
|---|---|---|
element
required
|
The element shown or hidden via hidden/data-state based on scroll. |
data-stimeo--scroll-visibility-target="element" |
Values
| Name | Description | Attribute |
|---|---|---|
offset
|
The scroll threshold in px for showing (offset mode) or the top reveal zone (direction mode); default 400. | data-stimeo--scroll-visibility-offset-value |
mode
|
Visibility logic: offset (show past offset) or direction (show on scroll up); default offset. |
data-stimeo--scroll-visibility-mode-value |
focusSelector
|
An optional selector to move focus to after toTop scrolls (default empty). |
data-stimeo--scroll-visibility-focus-selector-value |
root
|
An optional selector for the scroll source container; defaults to the window. | data-stimeo--scroll-visibility-root-value |
Actions
| Name | Description | Action |
|---|---|---|
toTop
|
Scrolls the source to the top and optionally moves focus to focusSelector, honoring reduced motion. |
stimeo--scroll-visibility#toTop |
Events
| Name | Description | Event |
|---|---|---|
change
|
Dispatched when visibility transitions; detail carries the visible boolean. | stimeo--scroll-visibility:change |
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 |
Element | Hidden below the threshold (also removed from the focus order). |
data-state |
Root element | "hidden" / "visible". |