scrollbar-gutter: The Layout Shift You Blamed on Everything Else
Navigate from a long page to a short one and the whole layout shifts a few pixels right. Open a modal that locks scrolling - everything jumps again. For years the workarounds were ugly (overflow-y: scroll forcing a permanent empty scrollbar track, or JS measuring scrollbar width and compensating with padding). The actual fix:
html {
scrollbar-gutter: stable;
}
The browser reserves the scrollbar's width whether or not a scrollbar is currently needed. Short pages, long pages, scroll-locked modals - the content column never moves. Unlike overflow-y: scroll, no disabled scrollbar is drawn in the empty state; the space is just held.
The modal case, specifically
The standard scroll-lock (body { overflow: hidden } while a dialog is open) removes the scrollbar, widening the content area - so opening any modal nudges the page behind it. With a stable gutter on html, the width stays constant through the lock. If you've implemented the classic padding-right compensation hack in JS, you can delete it.
Inner scroll containers too
.chat-messages {
overflow-y: auto;
scrollbar-gutter: stable;
}
Panels that fill and empty (chat logs, search results, dropdown lists) stop reflowing their content the moment they cross the overflow threshold. For symmetric padding when the design is centered, stable both-edges reserves matching space on both sides.
The overlay-scrollbar caveat
macOS (and mobile) default to overlay scrollbars that float above content and occupy zero layout width - there, scrollbar-gutter reserves nothing because there's nothing to reserve, and there was never a shift to fix. The property matters for Windows users, Linux, and anyone who sets "always show scrollbars" - which is a lot of your traffic even if your Mac never shows you the bug. This is a classic "works on my machine" class of issue: test with a mouse plugged into a MacBook or in a Windows VM.
Notes
- Support: Chromium 94+, Firefox 97+, Safari finally shipped it in 18.2 - so it's now safe as an enhancement everywhere, with old Safari simply keeping the old jump.
- It composes with scrollbar-width and scrollbar-color if you're theming scrollbars too. (Even if you aren't: thin scrollbars + stable gutter is a tasteful combination.)
- Measure the win: the jump shows up as CLS in field metrics when it happens after load (fonts, async content) - one line of CSS improving a Core Web Vital is good value.
Five-second fix, permanent result, and one fewer "why does the page twitch" ticket. The best kind of CSS.
Full-stack web developer sharing practical tutorials and building tools that ship.
Got something on your mind?
My inbox is open - no forms disappearing into the void here.
- Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
- Hire me for a project Have something custom in mind? Let's talk scope and timelines.
- Product support Bought something here? I'll help you get it running.
I usually reply within 1-2 business days.