サイトの配色は整えたのに、要素の中のスクロール領域だけ太くて明るいスクロールバーが出て浮いて見える——ダークテーマを作ったときに特に気になる部分です。この見た目を CSS の標準プロパティで調整できるのが scrollbar-width と scrollbar-color です。この記事では2つのプロパティの値と使いどころ、レイアウトのガタつきを防ぐ scrollbar-gutter、スクロールバーを隠すときのアクセシビリティ上の注意までを、実際に動かせるコード付きで解説します。
目次
スクロールバーの見た目が問題になる場面
スクロールバーは長いあいだ「ブラウザーと OS が勝手に描くもの」で、Web ページ側からは触れない部品でした。ページ全体のスクロールバーであればそれで困らないのですが、要素の中に作ったスクロール領域になると話が変わります。カードを横に並べたスライダー、コードの表示エリア、サイドバーのメニュー、モーダル内の長い本文——こうした小さな枠の中に OS 標準の太いバーが出ると、それだけでデザインの精度が下がって見えます。
とりわけ目立つのがダークテーマです。暗い背景のパネルに明るいグレーのスクロールバーが乗ると、その部分だけ切り抜いたように白く光って見えます。逆に暗い配色のスクロールバーを白い背景に置けば、今度は必要以上に主張します。周囲の色となじませたいという要求は、テーマカラーを持つサイトでは自然に出てくるものです。
scrollbar-width はスクロールバーの太さを、scrollbar-color はつまみと軌道の色を指定するプロパティです。どちらも継承するプロパティなので、親要素にまとめて指定すれば配下のスクロール領域に一括で効かせられます。
scrollbar-width で太さを切り替える
scrollbar-width が取る値は3つだけです。ピクセル数のような自由な指定はできず、「標準」「細い」「なし」から選ぶ形になっています。太さの具体的な値をブラウザーに委ねることで、OS ごとの操作感を壊さないようにする設計です。
| 値 | 効果 |
|---|---|
auto | 既定値。ブラウザーと OS が用意した標準の太さで表示する |
thin | 標準より細いスクロールバーを表示する。実際の太さはブラウザーに任される |
none | スクロールバーを表示しない。スクロールの操作自体は引き続きできる |
3つを並べて見比べてみましょう。それぞれの枠は height で高さを固定し、overflow-y: auto でスクロールを発生させています。none の枠もホイールやドラッグで問題なく動かせることを確かめてみてください。
<div class="grid">
<figure class="cell">
<div class="box auto">
<p>auto は既定値です。OS やブラウザーが用意した標準の幅でスクロールバーが表示されます。</p>
<p>この枠には高さが指定されていて、中身がはみ出すので縦スクロールが発生します。</p>
<p>スクロールしてバーの見た目を比べてみてください。</p>
<p>いちばん下の行です。</p>
</div>
<figcaption>auto(既定値)</figcaption>
</figure>
<figure class="cell">
<div class="box thin">
<p>thin は標準より細いスクロールバーです。幅の具体的な値はブラウザーに任されます。</p>
<p>この枠には高さが指定されていて、中身がはみ出すので縦スクロールが発生します。</p>
<p>スクロールしてバーの見た目を比べてみてください。</p>
<p>いちばん下の行です。</p>
</div>
<figcaption>thin(細い)</figcaption>
</figure>
<figure class="cell">
<div class="box none">
<p>none はスクロールバーを非表示にします。見えないだけで、スクロール自体はできます。</p>
<p>この枠には高さが指定されていて、中身がはみ出すので縦スクロールが発生します。</p>
<p>ホイールやドラッグで動かせることを確かめてみてください。</p>
<p>いちばん下の行です。</p>
</div>
<figcaption>none(非表示)</figcaption>
</figure>
</div>
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
font-family: sans-serif;
}
.cell {
margin: 0;
}
.cell figcaption {
margin-top: 6px;
font-size: 12px;
color: #555;
text-align: center;
}
/* スクロールが起きる箱。高さを固定して overflow を指定するのがポイント */
.box {
height: 140px;
overflow-y: auto;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 6px;
background: #fff;
font-size: 13px;
line-height: 1.7;
}
.box p {
margin: 0 0 12px;
}
/* 既定値。OS 標準の幅で表示される */
.auto {
scrollbar-width: auto;
}
/* 標準より細いスクロールバー */
.thin {
scrollbar-width: thin;
}
/* スクロールバーを消す。スクロール自体はできる */
.none {
scrollbar-width: none;
}
なお thin や none を指定しても、スクロールが起きない要素には何も変化がありません。表示上の見た目を変えるだけのプロパティなので、まず「その要素でスクロールが発生しているか」が前提になります。
scrollbar-color でつまみと軌道を塗り分ける
scrollbar-color は色を2つセットで指定します。1つ目が動く部分であるつまみ(thumb)、2つ目がその背後にある溝、軌道(track)の色です。順番に意味があるので入れ替えると印象が逆になりますし、片方だけを書くことはできません。1色だけ書いた宣言は無効になり、まるごと無視されます。
| 値 | 効果 |
|---|---|
auto | 既定値。ブラウザーと OS の既定の配色で表示する |
<色1> <色2> | 1つ目でつまみ(thumb)、2つ目で軌道(track)の色を指定する。必ず2色そろえて書く |
色の指定にはキーワード・16進数・rgb() など通常の色の書き方がそのまま使えます。テーマカラーを CSS 変数で持っているなら、scrollbar-color: var(--thumb) var(--track); のように渡せます。
下のデモでは、暗いパネルの中の縦スクロール領域と、カードを並べた横スクロール領域の両方に scrollbar-color と scrollbar-width: thin を指定しています。背景色に近い軌道と、少し明るいつまみという組み合わせにすると、パネルの一部として自然に見えます。
<div class="wrap">
<section class="panel">
<h3>ダークテーマのパネル</h3>
<div class="log">
<p>背景が暗い領域では、既定の明るいスクロールバーが浮いて見えることがあります。</p>
<p>scrollbar-color でつまみと軌道の色を指定すると、パネルの配色になじみます。</p>
<p>1つ目の値がつまみ(thumb)、2つ目の値が軌道(track)の色です。</p>
<p>色は2つセットで指定します。片方だけの指定はできません。</p>
<p>いちばん下の行です。</p>
</div>
</section>
<section class="panel">
<h3>カードの横スクロール</h3>
<div class="cards">
<div class="card">カード 1</div>
<div class="card">カード 2</div>
<div class="card">カード 3</div>
<div class="card">カード 4</div>
<div class="card">カード 5</div>
<div class="card">カード 6</div>
</div>
</section>
</div>
.wrap {
display: grid;
gap: 16px;
font-family: sans-serif;
}
.panel {
padding: 12px 14px;
border-radius: 8px;
background: #1e2233;
color: #e6e8f0;
}
.panel h3 {
margin: 0 0 10px;
font-size: 14px;
}
/* 縦スクロールする領域。高さ固定 + overflow でスクロールを発生させる */
.log {
height: 130px;
overflow-y: auto;
padding-right: 10px;
font-size: 13px;
line-height: 1.7;
/* つまみ(thumb)と軌道(track)の順に2色を指定する */
scrollbar-color: #7c8cff #2c3145;
scrollbar-width: thin;
}
.log p {
margin: 0 0 12px;
}
/* 横スクロールする領域 */
.cards {
display: flex;
gap: 10px;
overflow-x: auto;
padding-bottom: 10px;
scrollbar-color: #7c8cff #2c3145;
scrollbar-width: thin;
}
.card {
flex: 0 0 130px;
height: 70px;
display: grid;
place-items: center;
border-radius: 6px;
background: #2c3145;
font-size: 13px;
}
ページ全体のスクロールバーを変えたい場合は、html 要素(:root)に指定します。ルート要素に指定した値がビューポートのスクロールバーに反映される仕組みです。ダークモードの切り替えに合わせて色を変えたいなら、次のようにメディアクエリと組み合わせます。
/* ページ全体のスクロールバー。ルート要素に指定する */
html {
scrollbar-color: #b8bdd0 #eef0f6; /* つまみ 軌道 の順 */
}
/* OS がダークモードのときだけ配色を差し替える */
@media (prefers-color-scheme: dark) {
html {
scrollbar-color: #5a6178 #232838;
}
}
/* 継承するので、まとめ役の要素に書けば配下のスクロール領域すべてに効く */
.app {
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
}
この2つは継承されるプロパティなので、スクロール領域ひとつひとつに書かなくても、共通の親に一度書けば済みます。例外的に既定のままにしたい領域があれば、そこだけ scrollbar-color: auto で戻します。
scrollbar-gutter でレイアウトのガタつきを防ぐ
ここまでの2つと混同されやすいのが scrollbar-gutter ですが、これは別のプロパティで、役割もまったく違います。scrollbar-width / scrollbar-color がスクロールバーの見た目を決めるのに対して、scrollbar-gutter はスクロールバーが占める場所(溝)をあらかじめ確保しておくかを決めます。
解決したいのは、よくある「横にガタッと動く」現象です。内容が短いページではスクロールバーが出ず、長いページでは出るため、ページを移動するたびにコンテンツ幅がスクロールバーの太さぶん変わって、中央寄せのレイアウトが左右に飛びます。モーダルを開いたときに背景のスクロールを止めると同じことが起きます。scrollbar-gutter: stable を指定しておくと、スクロールバーが出ていないときも同じ幅の溝を空けておくので、幅が変わらず表示が安定します。
| 値 | 効果 |
|---|---|
auto | 既定値。スクロールバーが必要になったときだけ場所を取る |
stable | スクロールバーが表示されていなくても、その幅ぶんの溝を確保しておく |
stable both-edges | スクロールバーがある側だけでなく、反対側にも同じ幅の溝を確保して左右対称にする |
/* ページの横幅が伸び縮みするのを防ぐ */
html {
scrollbar-gutter: stable;
}
/* 要素内のスクロール領域にも使える */
.panel {
height: 320px;
overflow-y: auto;
scrollbar-gutter: stable;
scrollbar-width: thin;
}
注意したいのは、溝を確保するという性質上、スクロールバーが内容の上に重なって表示されるタイプ(オーバーレイ型)の環境では意味を持たないことです。macOS のようにスクロール中だけバーが浮かび上がる設定では、そもそも幅を取らないのでガタつきも起きません。同じ理由で scrollbar-width: none を指定した要素でも溝は作られません。「幅を確保するかどうか」の話なので、幅がゼロなら確保するものがない、と考えると分かりやすいはずです。
scrollbar-width: none で隠すときに考えること
横並びのカードスライダーなどで、スクロールバーを消してすっきり見せたくなる場面はよくあります。ただし scrollbar-width: none はスクロールできる事実そのものを隠してしまう指定でもあります。マウスホイール、タッチのスワイプ、トラックパッドの操作はこれまでどおり効きますが、画面上には「ここはまだ続きがある」という手がかりが一切残りません。初めて訪れた人が、見えている範囲だけで内容が終わっていると誤解する可能性があります。
キーボードで操作する人にも影響があります。スクロール領域そのものにフォーカスが当たるかどうかはブラウザーによって差があるので、中にリンクやボタンを含まない読み物系のスクロール領域では、tabindex="0" を付けてフォーカスできるようにし、あわせて role や aria-label で何の領域かを伝えておくと安全です。フォーカスできれば矢印キーや Page Down でスクロールできます。
<!-- スクロールバーを隠す領域は、キーボードでも操作できるようにしておく -->
<div class="log" tabindex="0" role="region" aria-label="実行ログ">
<p>長いテキストが続きます……</p>
</div>
そのうえで、消したスクロールバーの代わりになる視覚的な合図を用意しておくことをおすすめします。横スライダーなら端をグラデーションでフェードさせて「まだ先がある」と示す、次へ送るボタンを置く、カードを意図的に半分見切れさせる、といった方法です。thin にして細く目立たなくするだけで十分なことも多いので、none は「代わりの手がかりを用意できるとき」の選択肢と考えるとバランスが取れます。
::-webkit-scrollbar との関係
スクロールバーの装飾を調べると、::-webkit-scrollbar や ::-webkit-scrollbar-thumb といった記述もよく見つかります。これらは WebKit 由来のベンダー独自の擬似要素で、標準プロパティが整備される前から使われてきたものです。擬似要素なので border-radius や background-image、:hover まで指定でき、表現の自由度は標準プロパティより高い一方、あくまで独自仕様という位置づけです。
現在は scrollbar-width と scrollbar-color が主要ブラウザーで利用できるので、太さと色を整えたいだけなら標準プロパティだけで書くのがいちばん素直です。凝った装飾のために独自擬似要素を併用したい場合は、標準プロパティを土台にして、対応している環境だけ上乗せする形にします。
/* まず標準プロパティで基本の見た目を決める */
.panel {
scrollbar-width: thin;
scrollbar-color: #7c8cff #2c3145;
}
/* 独自擬似要素での上乗せ。対応していない環境ではまるごと無視される */
.panel::-webkit-scrollbar {
width: 8px;
}
.panel::-webkit-scrollbar-track {
background: #2c3145;
}
.panel::-webkit-scrollbar-thumb {
background: #7c8cff;
border-radius: 4px; /* 標準プロパティでは指定できない角丸 */
}
ただし、両方を同じ要素に書いたときにどちらが採用されるかはブラウザーの実装によって変わります。Chromium 系には「標準プロパティが指定されている要素では独自擬似要素の指定を適用しない」という挙動があり、::-webkit-scrollbar で作り込んだデザインが効かなくなったように見えることがあります。角丸などどうしても必要な装飾がないのであれば、どちらか一方に統一しておくのが結果的にいちばんトラブルが少ない選び方です。
指定してもスクロールバーの色が変わらないとき
そもそもスクロールが起きていない
いちばん多いのがこれです。scrollbar-width も scrollbar-color も実際にスクロールバーが表示される要素にしか効果がありません。高さを固定しないまま overflow-y: auto を書いても、要素は中身に合わせて伸びるだけでスクロールは発生しません。height や max-height、Flexbox / Grid の中なら min-height: 0 の指定を見直して、まず枠の中でスクロールが起きる状態を作ってください。
overflow が指定されていない
高さを固定しても overflow の指定がなければ、既定値の visible によって内容が枠からはみ出して表示されるだけで、スクロール領域にはなりません。縦なら overflow-y: auto、横なら overflow-x: auto をセットで書きます。scroll にすると中身が短いときも常にバーの場所を取るので、通常は auto を選びます。
色を1つしか書いていない
scrollbar-color: #7c8cff; のように1色だけ書くと、その宣言は無効な値としてまるごと捨てられます。つまみだけ色を変えたいときでも、軌道の色を一緒に指定しなければなりません。軌道は背景と同じ色にしておけば実質的につまみだけを色付けしたように見せられます。開発者ツールの Styles パネルで、その宣言に取り消し線が入っていないか確認するのが手早い切り分けです。
ページ全体に効かせたいのに body に書いている
ページ全体のスクロールバーはビューポートのもので、その色はルート要素の指定から決まります。body に書いても意図どおりにならないことがあるので、html(:root)に指定してください。逆に「ページ全体は既定のまま、この要素の中だけ変えたい」場合は、継承で降りてくる値を打ち消すためにその要素へ明示的に指定します。
::-webkit-scrollbar の指定と混ざっている
前の章で触れたとおり、標準プロパティと独自擬似要素を同じ要素に併記すると、どちらが効くかがブラウザーによって変わります。「Chrome では変わるのに Firefox では変わらない」「その逆」といった食い違いが出たときは、まず片方をコメントアウトして、どちらの指定が効いているのかを確かめると原因を切り分けられます。
OS やブラウザーの設定が優先されている
スクロールバーはユーザー環境の影響を強く受ける部品です。OS 側でスクロールバーを常時表示にしているか、スクロール中だけ表示する設定かによって見え方が変わりますし、ハイコントラストモードのようなアクセシビリティ設定が有効なときは、ユーザーの設定が優先されて指定した色が反映されないこともあります。手元の1台で思ったとおりに出ないときは、ページ側のコードだけでなく環境設定も疑ってみてください。
まとめ
scrollbar-width は auto / thin / none の3つでスクロールバーの太さを、scrollbar-color は「つまみ 軌道」の順に並べた2色で配色を決めます。どちらも継承するので、共通の親にまとめて書けば配下のスクロール領域を一括で整えられます。効かないときは、まずその要素で本当にスクロールが起きているかを確認するのが近道です。
名前の似た scrollbar-gutter は見た目ではなく溝の確保を担当する別のプロパティで、stable を指定するとスクロールバーの有無による横幅のガタつきを防げます。そして none でバーを隠すときは、スクロールできることを別の形で伝える工夫とキーボード操作への配慮を忘れないようにしましょう。細くする、色をなじませる、という控えめな調整だけでも、要素内のスクロール領域の見え方はかなり良くなります。