<input type="range">によるスライダーは、フォームで数値を入力するのに便利な UI 要素ですが、デフォルトの見た目はブラウザによって異なります。そこで、今回は CSS を使ってスライダーの見た目をカスタマイズする方法を紹介します。

LINE着せかえ「回るお寿司」販売中!

まずは簡単にできるカスタマイズ方法から

色を変えるだけなら accent-color だけで OK

基本的な見た目はブラウザ標準のままで、スライダーの基本色だけを変えるのであれば、accent-colorを指定するだけで OK です。

input[type="range"] {
  accent-color: green;
}

縦型にする場合は writing-mode を指定

通常、スライダーは横向きですが、writing-modevertical-rlもしくはvertical-lrを指定すると、スライダーを縦型にすることができます。

input[type="range"] {
  writing-mode: vertical-rl;
}

CSSでゴリゴリにカスタマイズする方法

さて、いよいよここからはゴリゴリにカスタマイズしていく方法を解説します。

まず、スライダーの構成についてですが、<input type="range">で表示されるスライダーは、以下のような要素で構成されています。

<input type="range">で表示されるスライダーの構成要素。track、thumb、progress の 3 つで構成されている。

この track, thumb, progress の各要素に対応する疑似要素にスタイルを当てることで見た目をカスタマイズしていきますが、2025 年 3 月時点では、ブラウザエンジンごとに異なるベンダープレフィックス付きの疑似要素を使用する必要があります。

このカスタマイズ方法は、2025 年 3 月現在、標準化されていない機能を使用しています。そのため、将来的に振る舞いが変わる可能性があります。
WebKit 系
(Chrome, Safari, Edge)
Gecko 系
(Firefox)
track::-webkit-slider-runnable-track::-moz-range-track
thumb::-webkit-slider-thumb::-moz-range-thumb
progressなし::-moz-range-progress

表を見るとわかるように、WebKit 系(Chrome, Safari, Edge)のブラウザについては progress に相当する疑似要素がありません。そのため、WebKit 系で progress を直接カスタマイズすることは、現在のところ不可能となっています。

しかし、JavaScript を併用して track の背景色を linear-gradient() で半分だけ色を付けるなどといった方法でなんとかカスタマイズすることが可能です。

ブラウザのデフォルトのスタイルをリセットする

まず、ブラウザごとにデフォルトで適用されているスタイルが異なるため、それを一旦リセットします。異なるベンダープレフィックスが付いた疑似要素は、まとめて指定することができないため、分けて記述します。

リセット用 CSS

input[type="range"] {
  appearance: none;
  touch-action: none;
  margin: 0;
  background: transparent;
}

input[type="range"]::-webkit-slider-thumb {
  appearance: none;
}

input[type="range"]::-moz-range-track,
input[type="range"]::-moz-range-thumb {
  border: none;
  box-sizing: border-box;
  background: transparent;
}
  • appearance: none; :
    ブラウザのデフォルトのスタイルをリセットし、カスタムデザインを適用しやすくするプロパティ。
  • touch-action: none; :
    タッチデバイスでのスクロールやズームなどを無効化するプロパティ。スライダーの操作中に画面がスクロールしてしまうのを防ぐ

この CSS を適用してリセットした<input type="range">の表示は以下のようになります。

…おそらく何も表示されていないかと思いますが存在しています。つまり、まっさらな状態に出来たということです。

疑似要素にスタイルを当てていく

さて、ここからそれぞれの疑似要素にスタイルを当てていきますが、先述したように異なるベンダープレフィックス付きの疑似要素は、まとめて指定することができないため、そのまま書くと同じスタイルを繰り返し書くことになります。そのため、スライダーの見た目に関する CSS は変数を使って定義しておくと便利です。

CSS 変数の定義

input[type="range"] {
  --track-width: 200px;
  --track-height: 6px;
  --track-color: #3A3A60;
  --track-border-width: 0;
  --track-border-color: transparent;
  --track-border-radius: 0;

  --thumb-width: 12px;
  --thumb-height: 28px;
  --thumb-color: #2AC5C6;
  --thumb-border-width: 2px;
  --thumb-border-color: #FFFFFF;
  --thumb-border-radius: 0;
  --thumb-shadow: -2px 2px 2px rgb(11 0 0 / 0.2);

  --progress-width: 50%;
  --progress-color: #2AC5C6;

  width: var(--track-width);
  height: max(var(--thumb-height), var(--track-height));
}

基本的には変数はこれだけあれば十分だと思いますが、必要に応じて変更してください。あとは、それぞれの変数を各疑似要素のプロパティに指定するだけです。

疑似要素に当てる CSS

input[type="range"]::-webkit-slider-runnable-track {
  height: var(--track-height);
  border: solid var(--track-border-width) var(--track-border-color);
  border-radius: var(--track-border-radius);
  background: var(--track-color);
  background: linear-gradient(to right, var(--progress-color) var(--progress-width), var(--track-color) var(--progress-width)) no-repeat;
}

input[type="range"]::-moz-range-track {
  height: var(--track-height);
  border: solid var(--track-border-width) var(--track-border-color);
  border-radius: var(--track-border-radius);
  background: var(--track-color);
  background: linear-gradient(to right, var(--progress-color) var(--progress-width), var(--track-color) var(--progress-width)) no-repeat;
}

input[type="range"]::-webkit-slider-thumb {
  margin-top: calc((var(--thumb-height) - var(--track-height)) / 2 * -1); /* この行だけ違う */
  width: var(--thumb-width);
  height: var(--thumb-height);
  border: solid var(--thumb-border-width) var(--thumb-border-color);
  border-radius: var(--thumb-border-radius);
  background: var(--thumb-color);
  box-shadow: var(--thumb-shadow);
}

input[type="range"]::-moz-range-thumb {
  width: var(--thumb-width);
  height: var(--thumb-height);
  border: solid var(--thumb-border-width) var(--thumb-border-color);
  border-radius: var(--thumb-border-radius);
  background: var(--thumb-color);
  box-shadow: var(--thumb-shadow);
}

なにやら沢山書いてありますが、WebKit 系(Chrome, Safari, Edge)と Gecko 系(Firefox)で違うのは WebKit 系のこの 1 行のみです。

margin-top: calc((var(--thumb-height) - var(--track-height)) / 2 * -1); /* この行だけ違う */

これは、WebKit 系のブラウザでは、thumb の起点が track の上辺からとなっていてズレが発生するので、そのズレを解消するための記述です。

この記述がないとこのようになる

JavaScript でスライダーの領域を塗り分ける

CSS の記述ができたら、次は JavaScript によるスライダーの塗り分けです。記述する JavaScript は以下になります。

document.addEventListener('DOMContentLoaded', () => {
  // スライダー要素を取得
  const rangeSliders = document.querySelectorAll('input[type="range"]');
 
  for(let i = 0; i < rangeSliders.length; i++) {
    // スライダー操作時の処理を設定
    rangeSliders[i].addEventListener("input", function() {
      updateSlider(this);
    });

    // 読み込み時にも反映
    updateSlider(rangeSliders[i]);
  }
 
  function updateSlider(slider) {
    // max 省略時は規定値である 100 を設定
    if(!slider.max) {
      slider.max = 100;
    }

    // 現在の値から割合を算出
    const progress = (slider.value / slider.max) * 100;
    // CSS 変数の値を書き換え
    slider.style.setProperty("--progress-width", progress + "%");
  }
});

選択されている値がスライダー全体の何割かを計算し、 linear-gradient() の塗りつぶしの割合を決める CSS 変数--progress-widthの値を書き換えるという処理になっています。

CSS と JavaScript によるカスタマイズのまとめ

ここまでに紹介した CSS と JavaScript をまとめたものと、それを実際に適用したスライダーは以下になります。

<input type="range">

<style>
input[type="range"] {
  appearance: none;
  touch-action: none;
  margin-block: 0;
  background: transparent;
}

input[type="range"]::-webkit-slider-thumb {
  appearance: none;
}

input[type="range"]::-moz-range-track,
input[type="range"]::-moz-range-thumb {
  border: none;
  box-sizing: border-box;
  background: transparent;
}

input[type="range"] {
  --track-width: 200px;
  --track-height: 6px;
  --track-color: #3A3A60;
  --track-border-width: 0;
  --track-border-color: transparent;
  --track-border-radius: 0;

  --thumb-width: 12px;
  --thumb-height: 28px;
  --thumb-color: #2AC5C6;
  --thumb-border-width: 2px;
  --thumb-border-color: #FFFFFF;
  --thumb-border-radius: 0;
  --thumb-shadow: -2px 2px 2px rgb(11 0 0 / 0.2);

  --progress-width: 50%;
  --progress-color: #2AC5C6;

  width: var(--track-width);
  height: max(var(--thumb-height), var(--track-height));
}

input[type="range"]::-webkit-slider-runnable-track {
  height: var(--track-height);
  border: solid var(--track-border-width) var(--track-border-color);
  border-radius: var(--track-border-radius);
  background: var(--track-color);
  background: linear-gradient(to right, var(--progress-color) var(--progress-width), var(--track-color) var(--progress-width)) no-repeat;
}

input[type="range"]::-moz-range-track {
  height: var(--track-height);
  border: solid var(--track-border-width) var(--track-border-color);
  border-radius: var(--track-border-radius);
  background: var(--track-color);
  background: linear-gradient(to right, var(--progress-color) var(--progress-width), var(--track-color) var(--progress-width)) no-repeat;
}

input[type="range"]::-webkit-slider-thumb {
  margin-top: calc((var(--thumb-height) - var(--track-height)) / 2 * -1); /* この行だけ違う */
  width: var(--thumb-width);
  height: var(--thumb-height);
  border: solid var(--thumb-border-width) var(--thumb-border-color);
  border-radius: var(--thumb-border-radius);
  background: var(--thumb-color);
  box-shadow: var(--thumb-shadow);
}

input[type="range"]::-moz-range-thumb {
  width: var(--thumb-width);
  height: var(--thumb-height);
  border: solid var(--thumb-border-width) var(--thumb-border-color);
  border-radius: var(--thumb-border-radius);
  background: var(--thumb-color);
  box-shadow: var(--thumb-shadow);
}
</style>

<script>
document.addEventListener('DOMContentLoaded', () => {
  // スライダー要素を取得
  const rangeSliders = document.querySelectorAll('input[type="range"]');
 
  for(let i = 0; i < rangeSliders.length; i++) {
    // スライダー操作時の処理を設定
    rangeSliders[i].addEventListener("input", function() {
      updateSlider(this);
    });

    // 読み込み時にも反映
    updateSlider(rangeSliders[i]);
  }
 
  function updateSlider(slider) {
    // max 省略時は規定値である 100 を設定
    if(!slider.max) {
      slider.max = 100;
    }

    // 現在の値から割合を算出
    const progress = (slider.value / slider.max) * 100;
    // CSS 変数の値を書き換え
    slider.style.setProperty("--progress-width", progress + "%");
  }
});
</script>

HTML にそのまま貼り付ければ動くので、いろいろ試して独自のデザインを仕上げてみてください!

参考

タグ: