demo · v138
Nested scrollers
Two scrollers, one inside the other. scrollIntoView({ container: "all" }) scrolls every ancestor; "nearest" stops at the closest scroll-container. The new container option finally gives you a clean choice — until v138 the only escape hatch was a hand-rolled scrollBy() loop up the parent chain.
Unflagged in Chrome 138.
Element.scrollIntoView({ container: 'all' | 'nearest' }) joins the existing block, inline, behavior options.
probing container option…
outer scroller — top spacer
inner scroller — start. The target sits 300px down inside this scroller.
target element
Calling target.scrollIntoView({container: "nearest"}) only moves the inner scroller. Calling target.scrollIntoView({container: "all"}) also brings the outer scroller into alignment.
inner scroller — end
outer scroller — bottom spacer
Call log
The API
// Pre-v138 — every ancestor scrolls, no opt-out
target.scrollIntoView();
// v138
target.scrollIntoView({
container: 'all', // scroll every scroll ancestor up to root (default behaviour)
block: 'center',
});
target.scrollIntoView({
container: 'nearest', // stop at the nearest scroll-container — don't disturb outer scrollers
block: 'center',
});
What's happening
- By default
scrollIntoViewwalks all the way up: every scroll ancestor gets adjusted to bring the target into view. Fine for a single scroller; bad UX for modals, popovers, nested chat threads. container: 'nearest'stops at the closest scrollable ancestor. The inner scroller moves; the outer page stays where it is.- This was the missing primitive for nested-scroll UIs — chat panes inside a modal, sub-lists inside a side-panel, focused list items inside a tab.
- Try the two options against the same target with different starting positions. Notice the outer scroller position changes only with
"all".