You're halfway down a page, an ad or a batch of new posts loads above you, and the paragraph you were reading slides off the screen. Since Safari 27, every major browser fights that jump for you. Scroll anchoring, and the overflow-anchor property that controls it, became Baseline on September 14, 2026.
What surprised me is how old this is: MDN's compatibility data lists Chrome 56 and Firefox 66. What was missing was Safari, and with it the iPhone.
What scroll anchoring does
The classic behaviour of a scroll container is to keep its scroll offset fixed. If something 300 px tall appears above what you're looking at, the offset stays the same, so everything you can see moves down by 300 px. The spec's introduction names the usual culprits: script inserting an ad iframe, or images without dimensions loading on a slow network.
Scroll anchoring flips that. The browser picks an anchor node inside the scroller, remembers where it is, and when a layout change moves it, adjusts the scroll offset by exactly the same amount. Your scroll position changes, but what you're looking at stays put.
That's what the demo above shows. Both panes are the same feed, the highlighted card is "where you are reading", and the dashed line marks where it was. Press "+ 3 posts above" or load the ad: on the left the card drops and the meter counts the pixels it moved; on the right the meter stays at 0 px and scrollTop grows instead. "Auto feed" keeps inserting posts every 1.4 seconds, which is the timeline-with-new-items situation where this matters most.
The code: it's on by default
There's nothing to turn on. The only CSS in the demo is the opt-out on the left pane:
.feed {
overflow-y: auto; /* scroll anchoring applies here by default */
}
.bad .feed {
overflow-anchor: none; /* the old behaviour: the content jumps */
}
overflow-anchor takes two values. auto, the initial value, makes an element eligible to be an anchor. none takes the element and its descendants out of anchor selection, and set on the scroller itself it disables anchoring for that scroller. The property doesn't inherit, but the spec notes you can't turn anchoring back on inside a none subtree, with one exception: a nested scroll container gets its own anchoring again unless it also says none.
How the anchor is chosen is worth knowing too. The spec's algorithm walks the scroller's children looking for a node that's fully visible, going deeper when a node is only partially visible, because deeper nodes are less likely to change internally. Elements with display: none, position: fixed, position: sticky or an absolute box positioned relative to something outside the scroller are skipped.
The fallback, if you need one
In browsers without native anchoring, the demo emulates it with the same idea in a few lines: note the reading card's position, insert the content, and compensate the difference.
if (!CSS.supports('overflow-anchor', 'auto')) {
const before = reader.offsetTop;
insertAbove(); // whatever adds content above
feed.scrollTop += reader.offsetTop - before; // put the reader back
}
offsetTop is relative to the feed because it has position: relative. I checked this snippet in Chromium with native anchoring switched off, and the card moved 0 px. Don't run it where anchoring is native: you'd compensate twice. In the demo, when the fallback is active, the right pane also gets overflow-anchor: none so only the script adjusts it.
Gotchas I hit or checked
- Nothing happens at the very top. The spec says that if the scroller isn't scrolled away from its origin, no anchor is selected. I tested it: with
scrollTopat 0, inserting a 50 px element above moved the content 52 px (the element plus its borders). That's deliberate: if you're at the top of a feed, you probably want to see the new items. - It only protects the anchor. While building the demo on a 375 px screen, the ad was first inserted between the visible top of the pane and the reading card. Following the spec's algorithm, the anchor is the first fully visible card from the top, which sat above the insertion point. That card stayed still, and my reading card got pushed down 150 px anyway. Correct per the spec, and a good reminder: anchoring compensates for changes above the anchor node, not for everything on screen.
- Suppression triggers. If, within the same suppression window (roughly, the same turn of the event loop), something changes
top/left/right/bottom,margin,padding, a width or height property,positionortransformon the path between the anchor node and the scroller, the adjustment is cancelled. - It fires scroll events. The adjustment counts as a scroll, so
scrolllisteners will see it. MDN's guide says that if a page misbehaves with anchoring, the usual reason is a scroll listener that doesn't expect this extra scrolling, and that Firefox can highlight the chosen anchor with thelayout.css.scroll-anchoring.highlightflag inabout:config. - It doesn't replace reserving space. Setting
widthandheighton images or amin-heighton ad slots still prevents the shift in the first place, including at the top of the page where anchoring stays out of the way.
What to keep an eye on
- Safari 27 in the wild. Baseline "newly available" means the latest versions support it, not that everyone has updated. Until older iPhones age out, Safari users on 26 and earlier still get the jump.
- Feeds and chat UIs. Prepending older messages or new posts was a classic place for hand-written
scrollTopfixes. Now that every engine anchors, those fixes can compensate twice, so it's a good moment to check if they're still needed. - The spec is still a draft. The suppression window, the slice of the event loop in which adjustments are batched, is only defined in this draft; the spec itself notes it should move into the HTML standard eventually.