CSSで、スクロールバーの有無でレイアウトがずれないようにする方法
ページが長くなってスクロールバーが出ると、その幅の分だけページの横幅が狭くなり、中央に寄せたヘッダーや本文が左へずれます。html に scrollbar-gutter: stable を指定すると、スクロールバーが出ていないときも同じ幅を空けておくので、ページを切り替えても中身の位置は変わりません。
検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。幅800px・高さ600pxの画面でページを開き、要素の位置と幅を getBoundingClientRect() で測りました。Playwright はヘッドレスで起動するとき、Chromium に --hide-scrollbars を付けます。そのままでは、長いページでも body の幅は800pxのままでした。そこでこの引数を外し、幅を取る通常のスクロールバー(この環境では15px)を出して測っています。Firefox と Safari、Windows と macOS、スマートフォン、中身の上に重ねて表示するスクロールバーの環境では試していません。

ずれるのは、スクロールバーの幅だけページが狭くなるから
検証に使ったのは、ヘッダーと本文を、幅560pxまでの入れ物で中央に寄せたページです。
/* 中央に寄せた、幅560pxまでの入れ物 */
.container {
max-width: 560px;
margin: 0 auto;
padding: 0 16px;
}
<header class="site-header">
<div class="container">
<span class="logo">それなら猫の手で</span>
<nav class="menu">……</nav>
</div>
</header>
<main class="container">
<p>本文の段落 1。……</p>
</main>
本文の段落を1つだけにした短いページと、40個にした長いページで、body の幅と、ヘッダーの入れ物の左端を測りました。
| ページ | スクロールバー | body の幅 |
ヘッダーの入れ物の左端 |
|---|---|---|---|
| 短い | なし | 800px | 104px |
| 長い | あり | 785px | 96.5px |
長いページでは、右にスクロールバーが出て、body の幅が15px狭くなりました。入れ物は残った幅の真ん中に置かれるので、左右の余白が7.5pxずつ減り、左端が104pxから96.5pxへずれます。記事一覧のような短いページと、記事のような長いページを行き来すると、ロゴやメニューがそのたびに横に動いて見えます。
html に scrollbar-gutter: stable を指定する
ずれを止めるには、html に次の1行を足します。
html {
scrollbar-gutter: stable;
}
html の指定 |
短いページの body の幅 |
短いページの左端 | 長いページの body の幅 |
長いページの左端 |
|---|---|---|---|---|
| 指定なし | 800px | 104px | 785px | 96.5px |
scrollbar-gutter: stable |
785px | 96.5px | 785px | 96.5px |
短いページでも body の幅が785pxになり、長いページと同じ位置に中身が置かれました。スクロールバーが出ていないときは、右端の15pxが何もない余白になります。
MDN の scrollbar-gutter の説明によると、このプロパティはスクロールバーを置く場所(ガター)を前もって空けておくためのものです。stable を指定すると、中身があふれていなくても、overflow が auto、scroll、hidden のどれかであればガターができます。
指定する場所は html です。同じ1行を body に書いたところ、短いページの左端は104pxのままで、ずれは止まりませんでした。CSS Overflow Module Level 3 の仕様では、ルート要素(html)に指定した scrollbar-gutter は画面全体のスクロールに使われ、overflow と違って body の指定は画面全体に引き継がれない、と決められています。
overflow-y: scroll でもずれは止まるが、空の枠が出る
ほかの方法として、html に overflow-y: scroll を指定して、スクロールバーを常に出しておくやり方もあります。位置を測ると、scrollbar-gutter: stable と同じ結果になりました。
html の指定 |
短いページの左端 | 長いページの左端 |
|---|---|---|
overflow-y: scroll |
96.5px | 96.5px |
scrollbar-gutter: stable |
96.5px | 96.5px |
違いは見た目です。overflow-y: scroll では、スクロールする必要のない短いページにも、スクロールバーの枠と上下の矢印が出ます。scrollbar-gutter: stable では、同じ場所は白い余白になります。

中身を画面の真ん中に保つなら both-edges を付ける
stable だけでは、短いページでも中身が7.5px左に寄った位置に置かれます。画面の真ん中からずらしたくないときは、both-edges を付けて、反対側(左)にも同じ幅を空けます。
html {
scrollbar-gutter: stable both-edges;
}
html の指定 |
body の幅 |
短いページの左端 | 長いページの左端 |
|---|---|---|---|
stable |
785px | 96.5px | 96.5px |
stable both-edges |
770px | 104px | 104px |
both-edges では、左右に15pxずつ空いて body の幅は770pxになり、左端はどちらのページでも104pxでした。スクロールバーのない短いページを、指定なしで開いたときと同じ位置です。そのかわり、body の幅は stable より15px狭くなるので、body の中の要素は、画面の左右15pxずつの部分には広がりません。
モーダルを開いて overflow: hidden にしてもずれない
モーダルを開いている間は、後ろのページがスクロールしないように、html に overflow: hidden を付けることがあります。このときもスクロールバーが消えるので、後ろのページが右へずれます。長いページで、overflow: hidden を付ける前と後の左端を測りました。
html の指定 |
付ける前 | overflow: hidden を付けた後 |
|---|---|---|
| 指定なし | 96.5px | 104px |
scrollbar-gutter: stable |
96.5px | 96.5px |
scrollbar-gutter: stable を指定しておけば、スクロールバーが消えたあともガターが残り、後ろのページは動きませんでした。先に引いた MDN の説明のとおり、overflow: hidden でも stable のガターはできます。スクロールバーの幅を JavaScript で測って padding-right を足す、といった処理は要りません。
幅 100vw の要素も、はみ出さなくなる
画面の端から端まで色を塗る帯などに width: 100vw を使うと、スクロールバーのある長いページでは、帯がスクロールバーの分だけはみ出して、横にもスクロールできるようになります。html の指定ごとに、帯の幅と横へのはみ出しを調べました。
html の指定 |
短いページの帯の幅 | 長いページの帯の幅 | 長いページの body の幅 |
長いページで横にはみ出すか |
|---|---|---|---|---|
| 指定なし | 800px | 800px | 785px | はみ出す |
scrollbar-gutter: stable |
785px | 785px | 785px | はみ出さない |
overflow-y: scroll |
785px | 785px | 785px | はみ出さない |
scrollbar-gutter: stable や overflow-y: scroll を指定すると、100vw はガターを除いた785pxになり、body の幅と同じになりました。CSS Values and Units Module Level 4 の仕様では、ルート要素の overflow や scrollbar-gutter によって、スクロールバーやその場所が常に出る場合は、vw などの単位をその分だけ小さくする、と決められています。仕様の変更履歴によると、scrollbar-gutter についてのこの決まりは、2023年12月18日の草案のあとに加わったものです。検証環境の Chromium 153 では仕様のとおりに動きましたが、100vw を使う場合は、対象のブラウザで帯の幅を確かめてください。
ページの中のスクロールする箱にも使える
scrollbar-gutter は、ページ全体だけでなく、高さを決めてスクロールさせるリストなどにも使えます。幅300px・高さ200pxのリストに、高さ30pxの項目を3個入れた場合と12個入れた場合で、項目の幅を測りました。
.list {
width: 300px;
height: 200px;
overflow-y: auto;
scrollbar-gutter: stable;
}
| リストの指定 | 項目3個 | 項目12個 |
|---|---|---|
overflow-y: auto だけ |
298px | 283px |
overflow-y: auto と scrollbar-gutter: stable |
283px | 283px |
scrollbar-gutter: stable だけ |
298px | 298px |
項目が増えてスクロールバーが出ても、項目の幅は283pxのままでした。ただし、overflow を書かずに scrollbar-gutter: stable だけを指定したリストでは、幅は空きませんでした。MDN の説明にあるとおり、ガターができるのは overflow が auto、scroll、hidden のときです。html の場合は、先に引いた CSS Overflow Module Level 3 の仕様で、画面全体の overflow: visible は auto として扱うと決められているので、scrollbar-gutter だけで足ります。
対応しているブラウザと、ガターができない環境
MDN の互換性データ(@mdn/browser-compat-data 8.1.8)で調べた、最初に対応したバージョンです。
| 機能 | Chrome | Firefox | Safari | iOS の Safari |
|---|---|---|---|---|
scrollbar-gutter(stable を含む) |
94 | 97 | 18.2 | 18.2 |
MDN の説明によると、スクロールバーには、場所を取る従来のものと、中身の上に重ねて表示するオーバーレイのものがあり、どちらを使うかはブラウザが決めます。オーバーレイのスクロールバーは場所を取らないので、stable を指定してもガターはできません。初期値の auto でも場所を取らないので、そもそもスクロールバーの有無で幅は変わりません。





