Manual UI Behaviors scrollFocus

scrollFocus

elements man ui/behaviors/scroll-focus

Makes a region that scrolls a Tab stop while it overflows, so a keyboard user can reach it and scroll it with the arrows, PageDown and Space. Without regions the root itself is the region; a region the author gave a tabindex is left alone.

Signature
function scrollFocus(root: HTMLElement, opts?: ScrollFocusOptions): () => void;

Options

Option Type Default What it does
regions string the root itself Selector for the scrolling regions inside root.
owns (el: HTMLElement) => boolean none Keeps only the regions that belong to root, so a nested panel's own are skipped.
marker string "data-scroll-stop" Attribute set on a region this made a stop, for styling its focus ring.
label string | ((el: HTMLElement) => string | null) none Accessible name for a stop that has no role and no name: the region gets role="region" and this aria-label while it is a stop, so a screen reader names what it landed on.

A region is measured when the root's content changes and when the region or its children resize. One that stops overflowing while focused keeps its stop until focus leaves, so focus is never thrown out of a panel. The stop function puts back what the behavior added, except on a region that still has focus.

In a template of your own

app/shared/templates/log-view/index.ehtml
import { scrollFocus } from "@elements/ui/behaviors"; interface LogViewState { stop?: () => void; } <LogView (lines: string[], private ui: LogViewState = {})> <pre class="log-view" oninsert={(e) => ui.stop = scrollFocus(e.target as HTMLElement, { label: "Log" })} onremove={() => ui.stop?.()}>{lines.join("\n")}</pre> </LogView>
app/shared/templates/log-view/style.css
.log-view { max-height: 20rem; overflow: auto; } .log-view[data-scroll-stop]:focus-visible { box-shadow: var(--focus-ring); }

Writes

tabindex="0" and the marker on a region while it overflows; with label, role="region" and aria-label on one that had no role or name. All put back by stop.

Used by

Dialog (opens in a new tab), Message (opens in a new tab), Popover (opens in a new tab), RichText (opens in a new tab), Sheet (opens in a new tab).