[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-scrollbar-gutter-layout-shift":44,"search-suggestions":60,"quiz-article-scrollbar-gutter-layout-shift":108,"path-membership-scrollbar-gutter-layout-shift":305},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"id":45,"slug":46,"kind":47,"title":48,"description":49,"config":50,"verticalId":5,"vertical":55,"course":52,"_count":56,"access":57,"attempts":59,"questionCount":51},"01a17bfb-4ad8-7383-afca-86949e8c83d1","scrollbar-gutter-layout-shift","PRACTICE_QUIZ","scrollbar-gutter: stop the modal flinch","Eight questions on why opening a modal shifts your page, and how scrollbar-gutter fixes it for good.",{"questionCount":51,"timeLimitSec":52,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":53,"maxAttempts":52,"revealAnswers":54,"allowFlagging":18,"allowBacktracking":17},8,null,70,"AFTER_SUBMIT",{"slug":6,"name":7},{"questions":51},{"allowed":17,"reason":58},"FREE",[],[61,65,69,73,77,81,85,89,93,97,100,104],{"slug":62,"name":63,"articles":64},"webdev","Webdev",164,{"slug":66,"name":67,"articles":68},"javascript","Javascript",132,{"slug":70,"name":71,"articles":72},"frontend","Frontend",93,{"slug":74,"name":75,"articles":76},"tutorial","Tutorial",65,{"slug":78,"name":79,"articles":80},"css","Css",50,{"slug":82,"name":83,"articles":84},"performance","Performance",27,{"slug":86,"name":87,"articles":88},"typescript","Typescript",24,{"slug":90,"name":91,"articles":92},"react","React",20,{"slug":94,"name":95,"articles":96},"programming","Programming",16,{"slug":98,"name":99,"articles":96},"browser","Browser",{"slug":101,"name":102,"articles":103},"node","Node",14,{"slug":105,"name":106,"articles":107},"html","Html",11,{"id":109,"slug":46,"title":110,"subtitle":52,"excerpt":111,"coverUrl":112,"locale":13,"readingMinutes":113,"publishedAt":114,"viewCount":115,"likeCount":19,"commentCount":19,"author":116,"vertical":121,"topic":122,"tags":124,"_count":129,"playground":131,"body":133,"bodyMd":290,"seo":291,"translationGroupId":295,"series":52,"podcastUrl":52,"verticalId":5,"thread":296,"assessments":298,"translations":301,"quiz":303},"01a17bfb-4a13-77fa-bb6c-5c654aec4dd7","I Deleted My Scroll-Lock Library's Ugly Hack. `scrollbar-gutter` Replaces It.","Opening a modal shifts your whole page sideways because the scrollbar vanishes with it — the bug every scroll-lock library papers over with a JS width measurement and a padding-right hack. `scrollbar-gutter: stable` fixes it in one line now that Safari finally caught up.","\u002Fmedia\u002Fcovers\u002Fscrollbar-gutter-layout-shift.png",6,"2026-11-02T05:31:34.059Z",32,{"id":117,"name":118,"username":119,"avatarUrl":52,"headline":120},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":78,"name":123},"CSS",[125,126,127,128],{"slug":78,"name":79,"color":52},{"slug":62,"name":63,"color":52},{"slug":70,"name":71,"color":52},{"slug":66,"name":67,"color":52},{"assessments":130},1,{"slug":46,"title":132},"scrollbar-gutter — interactive playground",{"blocks":134,"version":130},[135,139,142,146,149,152,158,161,164,167,170,173,176,180,183,187,190,194,197,200,204,207,210,213,216,219,222,226,229,232,235,238,241,244,247,250,253,256,259,262,265,272,275,278,281,284],{"id":136,"html":137,"type":138},"b1","\u003Cp>You open a modal. For one frame, before you can even read what it says, the page behind it flinches — the whole column of text shifts a few pixels to the right, like the browser flinched too.\u003C\u002Fp>","paragraph",{"id":140,"html":141,"type":138},"b2","\u003Cp>Close the modal and it snaps back. Nobody styled that. Nobody asked for it. And if you build UI on Windows or Linux, you&#39;ve shipped it to production without noticing, because on a Mac with the default trackpad scrollbars, it&#39;s nearly invisible. That&#39;s the part that makes this bug so sticky: the person who can see it and the person who has to fix it are usually not using the same operating system.\u003C\u002Fp>",{"id":143,"html":144,"text":144,"type":145,"level":31},"b3","The fix everyone reaches for first","heading",{"id":147,"html":148,"type":138},"b4","\u003Cp>The standard explanation is that \u003Ccode>overflow: hidden\u003C\u002Fcode> on \u003Ccode>&lt;body&gt;\u003C\u002Fcode> — the thing every modal, drawer, and dropdown uses to stop the page scrolling behind it — also removes the vertical scrollbar. Removing the scrollbar hands its width back to the content. The content reflows into that space. The page shifts.\u003C\u002Fp>",{"id":150,"html":151,"type":138},"b5","\u003Cp>So people fix it the only way CSS used to allow: measure the scrollbar, then pay it back in padding.\u003C\u002Fp>",{"id":153,"code":154,"type":155,"language":156,"highlight":157},"b6","function lockScroll() {\n  const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;\n  document.body.style.paddingRight = `${scrollbarWidth}px`;\n  document.body.style.overflow = \"hidden\";\n}\n\nfunction unlockScroll() {\n  document.body.style.paddingRight = \"\";\n  document.body.style.overflow = \"\";\n}","code","js",[],{"id":159,"html":160,"type":138},"b7","\u003Cp>This works. It&#39;s also the entire reason \u003Ccode>body-scroll-lock\u003C\u002Fcode>, \u003Ccode>react-remove-scroll\u003C\u002Fcode>, and half a dozen modal libraries ship their own copy of this exact measurement. Every one of them is solving the same problem: find out how wide the scrollbar \u003Cem>would have been\u003C\u002Fem>, then fake its presence with padding so nothing moves.\u003C\u002Fp>",{"id":162,"html":163,"type":138},"b8","\u003Cp>\u003Cstrong>Before you keep reading — guess how many pixels of padding a 100-line copy-pasted &quot;scroll lock&quot; utility is usually off by, on the one OS nobody tested it on.\u003C\u002Fstrong> The answer is almost always &quot;enough to notice,&quot; because scrollbar width isn&#39;t a constant. It&#39;s roughly 17px on Windows across Chrome and Firefox alike, 0px on a Mac with overlay scrollbars — and the measurement has to run \u003Cem>before\u003C\u002Fem> you lock, every single time, or it reads the wrong number.\u003C\u002Fp>",{"id":165,"html":166,"text":166,"type":145,"level":31},"b9","Why the shift happens at all",{"id":168,"html":169,"type":138},"b10","\u003Cp>Here&#39;s the part the padding hack never actually fixes, it just hides: a \u003Cem>classic\u003C\u002Fem> scrollbar — the opaque kind with a visible track, standard on Windows and Linux — is laid out inside the element&#39;s content box. The browser treats it the same way it treats a sidebar: it eats width from whatever would otherwise render there. Turn the scrollbar off, and the content box grows back to full width. Turn it on, and the content box shrinks to make room. The content isn&#39;t &quot;jumping&quot; — it&#39;s being resized, every time, and resized fast enough that it reads as a flinch.\u003C\u002Fp>",{"id":171,"html":172,"type":138},"b11","\u003Cp>macOS&#39;s default overlay scrollbars don&#39;t do this. They float on top of the content without claiming any layout space, so toggling them on and off never changes anything&#39;s width. That&#39;s the whole reason this bug is invisible to a developer who only ever tests on their own Mac: the platform they build on structurally can&#39;t reproduce the thing their users are reporting.\u003C\u002Fp>",{"id":174,"html":175,"type":138},"b12","\u003Cp>The padding-right hack works around the resize by adding back exactly the width the scrollbar would have taken — but it only works if you already know the scrollbar is about to disappear, and you measured it correctly, before it does. It&#39;s compensating for a resize instead of preventing one.\u003C\u002Fp>",{"id":177,"html":178,"text":179,"type":145,"level":31},"b13","\u003Ccode>scrollbar-gutter: stable\u003C\u002Fcode>: tell the layout to stop flinching","scrollbar-gutter: stable: tell the layout to stop flinching",{"id":181,"html":182,"type":138},"b14","\u003Cp>CSS has a property for exactly this now, and the name says what it does: reserve the scrollbar&#39;s gutter, \u003Cem>stably\u003C\u002Fem>, whether a scrollbar is currently needed or not.\u003C\u002Fp>",{"id":184,"code":185,"type":155,"language":78,"highlight":186},"b15","html {\n  scrollbar-gutter: stable;\n}",[],{"id":188,"html":189,"type":138},"b16","\u003Cp>Set this once, and the browser carves out the scrollbar&#39;s width in the layout permanently — for \u003Ccode>overflow: auto\u003C\u002Fcode>, \u003Ccode>scroll\u003C\u002Fcode>, \u003Cem>and\u003C\u002Fem> \u003Ccode>hidden\u003C\u002Fcode>. When your modal flips \u003Ccode>overflow\u003C\u002Fcode> to \u003Ccode>hidden\u003C\u002Fcode> to lock the page, the gutter is already reserved from before the modal ever opened. There&#39;s no width to give back, so there&#39;s nothing to resize, so there&#39;s nothing to flinch.\u003C\u002Fp>",{"id":191,"code":192,"type":155,"language":156,"highlight":193},"b17","function lockScroll() {\n  document.body.style.overflow = \"hidden\"; \u002F\u002F that's it — no measuring, no padding\n}\n\nfunction unlockScroll() {\n  document.body.style.overflow = \"\";\n}",[],{"id":195,"html":196,"type":138},"b18","\u003Cp>That&#39;s the whole fix for the layout-shift half of what a scroll-lock library does. You still need something to stop the page actually scrolling under a modal on touch devices — \u003Ccode>overflow: hidden\u003C\u002Fcode> alone doesn&#39;t catch every edge case there, and that part of a scroll-lock library can stay. But the width-measuring, padding-compensating dance that most of these libraries built their reputation on? \u003Ccode>scrollbar-gutter: stable\u003C\u002Fcode> makes it dead code.\u003C\u002Fp>",{"id":198,"html":199,"type":138},"b19","\u003Cp>There&#39;s a second value for layouts that center content and need symmetry on both sides:\u003C\u002Fp>",{"id":201,"code":202,"type":155,"language":78,"highlight":203},"b20","html {\n  scrollbar-gutter: stable both-edges;\n}",[],{"id":205,"html":206,"type":138},"b21","\u003Cp>\u003Ccode>both-edges\u003C\u002Fcode> mirrors the reserved gutter to the opposite inline edge, so if space is held open on the right, matching space opens on the left too — useful when a scrollbar appearing on only one side would visibly knock a centered layout off-center.\u003C\u002Fp>",{"id":208,"html":209,"type":138},"b22","\u003C!-- playground:start -->",{"id":211,"html":212,"text":212,"type":145,"level":31},"b23","🎮 Try it yourself",{"id":214,"html":215,"type":138},"b24","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fscrollbar-gutter-layout-shift\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":217,"html":218,"type":138},"b25","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":220,"html":221,"type":138},"b26","\u003C!-- playground:end -->",{"id":223,"html":224,"text":225,"type":145,"level":31},"b27","The part that&#39;s easy to get wrong: overlay scrollbars opt out of this too","The part that's easy to get wrong: overlay scrollbars opt out of this too",{"id":227,"html":228,"type":138},"b28","\u003Cp>Here&#39;s the honest asterisk, and it&#39;s the same one from earlier in reverse: \u003Ccode>scrollbar-gutter: stable\u003C\u002Fcode> only reserves space for \u003Cem>classic\u003C\u002Fem> scrollbars. MDN says it plainly — &quot;When using overlay scrollbars, the gutter will not be present.&quot; On a system with overlay scrollbars, setting this property changes nothing, because there was never any layout space being given up in the first place.\u003C\u002Fp>",{"id":230,"html":231,"type":138},"b29","\u003Cp>That means you can&#39;t fully verify this fix by eyeballing your own Mac. You need a Windows or Linux machine, a Windows\u002FLinux VM, or Chrome DevTools&#39; device toolbar won&#39;t help either — it emulates viewport size, not the host OS&#39;s scrollbar rendering mode. The honest test is someone, or something, running an actual classic-scrollbar environment.\u003C\u002Fp>",{"id":233,"html":234,"text":234,"type":145,"level":31},"b30","Is this safe to ship today?",{"id":236,"html":237,"type":138},"b31","\u003Cp>Yes, with one small caveat worth knowing rather than discovering. Chrome and Edge have supported \u003Ccode>scrollbar-gutter\u003C\u002Fcode> since version 94 (2021), and Firefox since version 97 (2022) — this has quietly worked for most of your users for years. Safari was the holdout; it landed support in Safari 18.2, released in December 2024, which is also what pushed \u003Ccode>scrollbar-gutter\u003C\u002Fcode> to Baseline &quot;newly available&quot; that same month. If you need to support Safari versions older than 18.2, the property is simply ignored there — no error, no warning, the page just behaves exactly like it always did. That&#39;s a safe degrade, not a broken one, so there&#39;s no reason to gate this behind a feature query unless you want to show a different UI specifically for browsers that lack it.\u003C\u002Fp>",{"id":239,"html":240,"type":138},"b32","\u003Cp>The next time a modal makes your page twitch, you&#39;ll know exactly why — and now you know the one-line reason to stop paying JavaScript to measure a number CSS can just reserve for you.\u003C\u002Fp>",{"id":242,"html":243,"type":138},"b33","\u003Cp>Still carrying a scroll-lock library in your bundle for this exact problem? Go check what it actually does under the hood — there&#39;s a decent chance half of it just became unnecessary.\u003C\u002Fp>",{"id":245,"html":246,"type":138},"b34","\u003C!-- quiz:start -->",{"id":248,"html":249,"text":249,"type":145,"level":31},"b35","🧠 Test yourself",{"id":251,"html":252,"type":138},"b36","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fscrollbar-gutter-layout-shift\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":254,"html":255,"type":138},"b37","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":257,"html":258,"type":138},"b38","\u003C!-- quiz:end -->",{"id":260,"html":261,"type":138},"b39","\u003C!-- related:start -->",{"id":263,"html":264,"text":264,"type":145,"level":31},"b40","📚 Read next",{"id":266,"type":267,"items":268,"ordered":18},"b41","list",[269,270,271],"\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-grid-lanes-native-masonry-layout\">Stop Using a JS Masonry Library. CSS Grid Lanes Does It Natively.\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-trigonometric-functions-radial-layout\">Stop Writing Math.cos() in a Resize Listener\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Flayout-thrashing-forced-reflow\">Your &#39;Expand All&#39; Button Is Quietly Causing Layout Thrashing\u003C\u002Fa>",{"id":273,"html":274,"type":138},"b42","\u003C!-- related:end -->",{"id":276,"type":277},"b43","divider",{"id":279,"html":280,"type":138},"b44","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":282,"html":283,"type":138},"b45","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":285,"type":267,"items":286,"ordered":18},"b46",[287,288,289],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","You open a modal. For one frame, before you can even read what it says, the page behind it flinches — the whole column of text shifts a few pixels to the right, like the browser flinched too.\n\nClose the modal and it snaps back. Nobody styled that. Nobody asked for it. And if you build UI on Windows or Linux, you've shipped it to production without noticing, because on a Mac with the default trackpad scrollbars, it's nearly invisible. That's the part that makes this bug so sticky: the person who can see it and the person who has to fix it are usually not using the same operating system.\n\n## The fix everyone reaches for first\n\nThe standard explanation is that `overflow: hidden` on `\u003Cbody>` — the thing every modal, drawer, and dropdown uses to stop the page scrolling behind it — also removes the vertical scrollbar. Removing the scrollbar hands its width back to the content. The content reflows into that space. The page shifts.\n\nSo people fix it the only way CSS used to allow: measure the scrollbar, then pay it back in padding.\n\n```js\nfunction lockScroll() {\n  const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;\n  document.body.style.paddingRight = `${scrollbarWidth}px`;\n  document.body.style.overflow = \"hidden\";\n}\n\nfunction unlockScroll() {\n  document.body.style.paddingRight = \"\";\n  document.body.style.overflow = \"\";\n}\n```\n\nThis works. It's also the entire reason `body-scroll-lock`, `react-remove-scroll`, and half a dozen modal libraries ship their own copy of this exact measurement. Every one of them is solving the same problem: find out how wide the scrollbar *would have been*, then fake its presence with padding so nothing moves.\n\n**Before you keep reading — guess how many pixels of padding a 100-line copy-pasted \"scroll lock\" utility is usually off by, on the one OS nobody tested it on.** The answer is almost always \"enough to notice,\" because scrollbar width isn't a constant. It's roughly 17px on Windows across Chrome and Firefox alike, 0px on a Mac with overlay scrollbars — and the measurement has to run *before* you lock, every single time, or it reads the wrong number.\n\n## Why the shift happens at all\n\nHere's the part the padding hack never actually fixes, it just hides: a *classic* scrollbar — the opaque kind with a visible track, standard on Windows and Linux — is laid out inside the element's content box. The browser treats it the same way it treats a sidebar: it eats width from whatever would otherwise render there. Turn the scrollbar off, and the content box grows back to full width. Turn it on, and the content box shrinks to make room. The content isn't \"jumping\" — it's being resized, every time, and resized fast enough that it reads as a flinch.\n\nmacOS's default overlay scrollbars don't do this. They float on top of the content without claiming any layout space, so toggling them on and off never changes anything's width. That's the whole reason this bug is invisible to a developer who only ever tests on their own Mac: the platform they build on structurally can't reproduce the thing their users are reporting.\n\nThe padding-right hack works around the resize by adding back exactly the width the scrollbar would have taken — but it only works if you already know the scrollbar is about to disappear, and you measured it correctly, before it does. It's compensating for a resize instead of preventing one.\n\n## `scrollbar-gutter: stable`: tell the layout to stop flinching\n\nCSS has a property for exactly this now, and the name says what it does: reserve the scrollbar's gutter, *stably*, whether a scrollbar is currently needed or not.\n\n```css\nhtml {\n  scrollbar-gutter: stable;\n}\n```\n\nSet this once, and the browser carves out the scrollbar's width in the layout permanently — for `overflow: auto`, `scroll`, *and* `hidden`. When your modal flips `overflow` to `hidden` to lock the page, the gutter is already reserved from before the modal ever opened. There's no width to give back, so there's nothing to resize, so there's nothing to flinch.\n\n```js\nfunction lockScroll() {\n  document.body.style.overflow = \"hidden\"; \u002F\u002F that's it — no measuring, no padding\n}\n\nfunction unlockScroll() {\n  document.body.style.overflow = \"\";\n}\n```\n\nThat's the whole fix for the layout-shift half of what a scroll-lock library does. You still need something to stop the page actually scrolling under a modal on touch devices — `overflow: hidden` alone doesn't catch every edge case there, and that part of a scroll-lock library can stay. But the width-measuring, padding-compensating dance that most of these libraries built their reputation on? `scrollbar-gutter: stable` makes it dead code.\n\nThere's a second value for layouts that center content and need symmetry on both sides:\n\n```css\nhtml {\n  scrollbar-gutter: stable both-edges;\n}\n```\n\n`both-edges` mirrors the reserved gutter to the opposite inline edge, so if space is held open on the right, matching space opens on the left too — useful when a scrollbar appearing on only one side would visibly knock a centered layout off-center.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fscrollbar-gutter-layout-shift\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## The part that's easy to get wrong: overlay scrollbars opt out of this too\n\nHere's the honest asterisk, and it's the same one from earlier in reverse: `scrollbar-gutter: stable` only reserves space for *classic* scrollbars. MDN says it plainly — \"When using overlay scrollbars, the gutter will not be present.\" On a system with overlay scrollbars, setting this property changes nothing, because there was never any layout space being given up in the first place.\n\nThat means you can't fully verify this fix by eyeballing your own Mac. You need a Windows or Linux machine, a Windows\u002FLinux VM, or Chrome DevTools' device toolbar won't help either — it emulates viewport size, not the host OS's scrollbar rendering mode. The honest test is someone, or something, running an actual classic-scrollbar environment.\n\n## Is this safe to ship today?\n\nYes, with one small caveat worth knowing rather than discovering. Chrome and Edge have supported `scrollbar-gutter` since version 94 (2021), and Firefox since version 97 (2022) — this has quietly worked for most of your users for years. Safari was the holdout; it landed support in Safari 18.2, released in December 2024, which is also what pushed `scrollbar-gutter` to Baseline \"newly available\" that same month. If you need to support Safari versions older than 18.2, the property is simply ignored there — no error, no warning, the page just behaves exactly like it always did. That's a safe degrade, not a broken one, so there's no reason to gate this behind a feature query unless you want to show a different UI specifically for browsers that lack it.\n\nThe next time a modal makes your page twitch, you'll know exactly why — and now you know the one-line reason to stop paying JavaScript to measure a number CSS can just reserve for you.\n\nStill carrying a scroll-lock library in your bundle for this exact problem? Go check what it actually does under the hood — there's a decent chance half of it just became unnecessary.\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 8-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fscrollbar-gutter-layout-shift\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\n\u003C!-- related:start -->\n\n## 📚 Read next\n\n- [Stop Using a JS Masonry Library. CSS Grid Lanes Does It Natively.](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-grid-lanes-native-masonry-layout)\n- [Stop Writing Math.cos() in a Resize Listener](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-trigonometric-functions-radial-layout)\n- [Your 'Expand All' Button Is Quietly Causing Layout Thrashing](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Flayout-thrashing-forced-reflow)\n\n\u003C!-- related:end -->\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":292,"canonical":293,"description":294},"I Deleted My Scroll-Lock Library's Ugly Hack. `scrollbar-gutter` Repla","https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fscrollbar-gutter-layout-shift","Opening a modal shifts your whole page sideways because the scrollbar vanishes with it — the bug every scroll-lock library papers over with a JS width measurement and a padding-rig","01a17bfb-4a13-77fa-bb6c-60403467e618",{"id":297,"locked":18},"01a17bfb-4ab3-767a-a996-63bd84d777c8",[299],{"id":45,"slug":46,"title":48,"_count":300},{"questions":51},[302],{"locale":13,"slug":46},{"id":45,"slug":46,"title":48,"_count":304,"questionCount":51},{"questions":51},[]]