CSS

CSSでチェックボックスをカスタマイズする方法|3パターンのデザインを徹底解説

みなと

Webサイト制作の現場では、デザインカンプ通りのチェックボックスを実装しなければならない場面が頻繁にあります。しかし、デフォルトのチェックボックスはブラウザが独自のスタイルを持っているため、CSSで見た目を変えようとしてもうまくいかないことがほとんどです。

UI実装担当
UI実装担当

どうすればオリジナルのデザインに変えられるの?

と困った経験はありませんか?

この記事では、CSSでチェックボックスを自由にカスタマイズする方法を、3つのデザインパターンで解説します。シンプルな正方形からカード型、トグルスイッチまで、レスポンシブにも対応したコードとともに紹介します。

なお、ラジオボタンのカスタマイズ方法については、以下の記事で詳しく解説しています。あわせてご覧ください。

あわせて読みたい
CSSでラジオボタンをカスタマイズする方法|3パターンのデザインを徹底解説
CSSでラジオボタンをカスタマイズする方法|3パターンのデザインを徹底解説

デフォルトのチェックボックスはCSSで変えにくい

デフォルトのチェックボックスは、ブラウザが独自のスタイルを持っているため、CSSで見た目を変えようとしても思い通りにならないことがほとんどです。appearance プロパティでスタイルをリセットする方法もありますが、ブラウザによって挙動が異なるため完全にコントロールするのが難しいのが現状です。

なお、現在の主要ブラウザではデフォルトの見た目の差は以前ほど大きくありませんが、環境によって微妙な違いが生じることがあります。

PC(Windows)のEdge・Chrome・Firefox、PC(macOS)のSafari・Chrome・Firefox、タブレット(iPadOS)のSafari、スマホ(iOS)のSafari、スマホ(Android)のChromeにおけるデフォルトのチェックボックス表示を並べた比較画像
同じHTMLでも、OS・ブラウザ・端末によって見た目に差が出ることがあります。

そのため、この記事ではデフォルトのチェックボックスを非表示にして独自のUIを組み立てるアプローチを紹介します。これにより、どの環境でも同じデザインに揃えることができます。

CSSでチェックボックスをカスタマイズする基本的な考え方

チェックボックスをCSSでカスタマイズするには、デフォルトのチェックボックスを見えない状態にして、独自のUIを別途HTMLで組み立てるアプローチを取ります。

具体的には、input[type="checkbox"] を画面上から隠しつつ、label 要素の中に独自デザインの要素(span など)を用意して、そちらにスタイルを当てます。input の状態(チェック済み・disabledなど)はCSSのセレクターで参照できるため、JavaScriptなしで動的なスタイル切り替えが実現できます。

非表示にしたinput要素と、デザインを担うspan要素の関係を示した図。input要素は「非表示」、span要素は「自由にデザイン」とラベリングされており、隣接セレクタで連携する仕組みを図解している。

visually-hiddenとは

input を非表示にする方法として display: none が思い浮かぶかもしれませんが、この記事では visually-hidden という手法を使います。

display: none はスクリーンリーダー(読み上げソフト)にも認識されなくなるため、視覚に頼らないユーザーがチェックボックスを操作できなくなってしまいます。visually-hiddenは、見た目上は非表示にしつつ、スクリーンリーダーには認識させることができる手法です。

input[type="checkbox"] {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

要素を1px×1pxまで縮小し、clip ではみ出た部分を切り取ることで、画面上には見えない状態にしています。フォームのアクセシビリティを保つために、display: none よりもこの方法を推奨します。

パターン1 — シンプル正方形

まず最もベーシックな、シンプルな正方形のチェックボックスです。デフォルトに近いUI構造を保ちながら、見た目を整えたいときに使いやすいパターンです。

完成デモの紹介

完成したデモです。実際に操作して動作を確認してみてください。

コード全体(HTML / CSS)

HTMLコード

<ul class="cb-simple-list">
  <li>
    <label>
      <input type="checkbox" name="lang" value="html" checked>
      <span class="box"></span>
      <span class="text">HTML</span>
    </label>
  </li>
  <li>
    <label>
      <input type="checkbox" name="lang" value="css" checked>
      <span class="box"></span>
      <span class="text">CSS</span>
    </label>
  </li>
  <li>
    <label>
      <input type="checkbox" name="lang" value="js">
      <span class="box"></span>
      <span class="text">JavaScript</span>
    </label>
  </li>
  <li>
    <label>
      <input type="checkbox" name="lang" value="ts" disabled>
      <span class="box"></span>
      <span class="text">TypeScript(disabled)</span>
    </label>
  </li>
</ul>

CSSコード

.cb-simple-list {
  list-style: none;
  display: flex;
  margin: 0;
  padding: 0;
}

@media (min-width: 768px) {
  .cb-simple-list {
    justify-content: center;
    gap: 40px;
  }
}

@media (max-width: 767px) {
  .cb-simple-list {
    flex-direction: column;
    gap: 15px;
  }
}

.cb-simple-list label {
  display: block;
  position: relative;
  padding-left: 38px;
  font-size: 18px;
  line-height: 1.5;
  cursor: pointer;
}

.cb-simple-list label:has(input:disabled) {
  cursor: not-allowed;
}

.cb-simple-list input[type="checkbox"] {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

.cb-simple-list .box {
  position: absolute;
  left: 0;
  top: 0;
  width: 28px;
  height: 28px;
  box-sizing: border-box;
  border: 1px solid #058ad5;
  border-radius: 4px;
  background: #fff;
  transition: box-shadow 300ms ease;
}

.cb-simple-list .box::before {
  content: "";
  display: block;
  position: absolute;
  left: 50%;
  top: 50%;
  width: 6px;
  height: 12px;
  border-right: 2px solid #fff;
  border-bottom: 2px solid #fff;
  transform: translate(-50%, -65%) rotate(45deg);
  visibility: hidden;
}

.cb-simple-list label:not(:has(input:disabled)):hover .box,
.cb-simple-list input:focus-visible + .box {
  box-shadow: 0 0 15px rgba(5, 138, 213, 0.4);
}

.cb-simple-list input:checked + .box {
  background: #058ad5;
}

.cb-simple-list input:checked + .box::before {
  visibility: visible;
}

.cb-simple-list input:disabled + .box {
  border-color: #999;
  opacity: 0.3;
}

.cb-simple-list input:disabled ~ .text {
  opacity: 0.3;
}

コードのポイント解説

パターン1のコードについて、特に重要なポイントを5つ解説します。

ポイント1:label全体をクリック領域にする

label 要素で input.box.text をまとめて囲むことで、テキスト部分をクリックしてもチェックボックスが切り替わるようになります。

また、通常時は cursor: pointer を指定していますが、disabled時はクリックできないため cursor: not-allowed に切り替えます。:has() を使うことで、label の中に input:disabled が存在するときだけスタイルを当てることができます。

.cb-simple-list label {
  cursor: pointer;
}

.cb-simple-list label:has(input:disabled) {
  cursor: not-allowed;
}
スワン
スワン

:has() は比較的新しいCSSですが、主要ブラウザではすでに対応済みです。

ポイント2:チェックボックスの正方形とチェックマークを描く

visually-hiddenで input を非表示にしている代わりに、.box という span 要素でチェックボックスのインジケーター(四角い部分)を再現しています。

border-radius: 4px で角を少し丸めた正方形にし、チェックマークは ::before 疑似要素で表現しています。幅と高さの異なる長方形の右辺と底辺にだけ border を指定し、rotate(45deg) で斜めに傾けることでチェックマークの形を作っています。position: absolutetransform: translate(-50%, -65%) で中央へ配置しています。

.cb-simple-list .box {
  position: absolute;
  width: 28px;
  height: 28px;
  border: 1px solid #058ad5;
  border-radius: 4px;
  background: #fff;
}

.cb-simple-list .box::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 6px;
  height: 12px;
  border-right: 2px solid #fff;
  border-bottom: 2px solid #fff;
  transform: translate(-50%, -65%) rotate(45deg);
  visibility: hidden;
}

ポイント3:ホバー・フォーカス時に光彩を表示する

ホバー時とキーボードフォーカス時に、box-shadow で青い光彩を表示してフィードバックを伝えます。

disabled時にもホバーが発火しないよう、:not(:has(input:disabled)) で除外しています。キーボード操作時のフォーカスは :focus-visible で対応しています。

.cb-simple-list label:not(:has(input:disabled)):hover .box,
.cb-simple-list input:focus-visible + .box {
  box-shadow: 0 0 15px rgba(5, 138, 213, 0.4);
}
みなと
みなと

:not(:has(input:disabled)) の部分、少し複雑に見えますが、意味は「disabled状態のinputを含まないlabel」です。

ポイント4:選択状態のスタイルを切り替える

input がチェックされたとき(:checked)、隣接する .box にスタイルを当てます。+ は隣接兄弟結合子で、直後の兄弟要素1つだけを対象にします。

チェック時は .box の背景色を青に変え、::beforevisibility: visible に切り替えることで、チェックマークを表示しています。

.cb-simple-list input:checked + .box {
  background: #058ad5;
}

.cb-simple-list input:checked + .box::before {
  visibility: visible;
}

ポイント5:disabled時の見た目を表現する

disabled時は操作できないことをユーザーに伝えるため、.boxopacity を下げて薄く見せ、border-color をグレーに変えています。テキスト(.text)も同様に opacity を下げて、全体的に非活性であることを視覚的に示します。

.cb-simple-list input:disabled + .box {
  border-color: #999;
  opacity: 0.3;
}

.cb-simple-list input:disabled ~ .text {
  opacity: 0.3;
}

.text には + ではなく ~(一般兄弟結合子)を使っています。input.text の間に .box が挟まっているため、直後の要素だけを対象にする + では届かないためです。

パターン2 — カード型

次に、選択肢全体がカードになるデザインです。複数の選択肢を視覚的に強調したい場面や、ボタン感のあるUIにしたいときに向いているパターンです。

完成デモの紹介

完成したデモです。実際に操作して動作を確認してみてください。

コード全体(HTML / CSS)

HTMLコード

<ul class="cb-card-list">
  <li>
    <label>
      <input type="checkbox" name="tool" value="vscode" checked>
      <span class="card">
        <span class="card-text">VS Code</span>
        <span class="check-icon">
          <svg width="10" height="8" viewBox="0 0 10 8" fill="none" aria-hidden="true">
            <path d="M1 4l3 3 5-6" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
          </svg>
        </span>
      </span>
    </label>
  </li>
  <li>
    <label>
      <input type="checkbox" name="tool" value="git" checked>
      <span class="card">
        <span class="card-text">Git</span>
        <span class="check-icon">
          <svg width="10" height="8" viewBox="0 0 10 8" fill="none" aria-hidden="true">
            <path d="M1 4l3 3 5-6" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
          </svg>
        </span>
      </span>
    </label>
  </li>
  <li>
    <label>
      <input type="checkbox" name="tool" value="figma">
      <span class="card">
        <span class="card-text">Figma</span>
        <span class="check-icon">
          <svg width="10" height="8" viewBox="0 0 10 8" fill="none" aria-hidden="true">
            <path d="M1 4l3 3 5-6" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
          </svg>
        </span>
      </span>
    </label>
  </li>
  <li>
    <label>
      <input type="checkbox" name="tool" value="notion" disabled>
      <span class="card">
        <span class="card-text">Notion(disabled)</span>
        <span class="check-icon">
          <svg width="10" height="8" viewBox="0 0 10 8" fill="none" aria-hidden="true">
            <path d="M1 4l3 3 5-6" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
          </svg>
        </span>
      </span>
    </label>
  </li>
</ul>

CSSコード

.cb-card-list {
  list-style: none;
  display: flex;
  margin: 0;
  padding: 0;
}

@media (min-width: 768px) {
  .cb-card-list {
    justify-content: center;
    gap: 15px;
  }
}

@media (max-width: 767px) {
  .cb-card-list {
    flex-direction: column;
    gap: 10px;
  }
}

.cb-card-list label {
  display: block;
  position: relative;
  font-size: 18px;
  line-height: 1.5;
  cursor: pointer;
}

.cb-card-list label:has(input:disabled) {
  cursor: not-allowed;
}

.cb-card-list input[type="checkbox"] {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

.cb-card-list .card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 15px;
  border: 1px solid #ccc;
  border-radius: 6px;
  background: #fff;
  transition: border-color 300ms ease, box-shadow 300ms ease;
}

.cb-card-list .check-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  border: 1px solid #ccc;
  border-radius: 50%;
  background: #fff;
}

.cb-card-list .check-icon svg {
  display: none;
}

.cb-card-list label:not(:has(input:disabled)):hover .card,
.cb-card-list input:focus-visible + .card {
  border-color: #058ad5;
  box-shadow: 0 0 15px rgba(5, 138, 213, 0.4);
}

.cb-card-list input:checked + .card {
  border-color: #058ad5;
  background: #e6f1fb;
}

.cb-card-list input:checked + .card .check-icon {
  border-color: #058ad5;
  background: #058ad5;
}

.cb-card-list input:checked + .card .check-icon svg {
  display: block;
}

.cb-card-list input:disabled + .card {
  opacity: 0.3;
}

コードのポイント解説

クリック領域・ホバー・フォーカス・disabled時の制御については、パターン1と同じアプローチを採用しています。ここではパターン2固有のポイントを解説します。

ポイント1:カードUIを作る

パターン1では .box という独立したインジケーターを用意しましたが、このパターンでは .card というひとつの span にテキストとチェックアイコンをまとめて持たせます。display: flexjustify-content: space-between を指定することで、テキストを左端・チェックアイコンを右端に配置しています。

.cb-card-list .card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 15px;
  border: 1px solid #ccc;
  border-radius: 6px;
  background: #fff;
}

ポイント2:選択状態のスタイルを切り替える

チェック時は input:checked + .cardカード全体の背景色・ボーダー色をまとめて変えます。さらに、カード内の .check-icon(孫要素)のスタイルも同時に切り替えます。+.card を参照したあと、スペースで .check-icon を指定することで孫要素にも届かせることができます。

.cb-card-list input:checked + .card {
  border-color: #058ad5;
  background: #e6f1fb;
}

.cb-card-list input:checked + .card .check-icon {
  border-color: #058ad5;
  background: #058ad5;
}

ポイント3:SVGチェックアイコンの表示を切り替える

チェックアイコンのSVGは、通常時は display: none で非表示にしておき、チェック時だけ display: block に切り替えて表示します。

.cb-card-list .check-icon svg {
  display: none;
}

.cb-card-list input:checked + .card .check-icon svg {
  display: block;
}

ポイント4:装飾SVGをスクリーンリーダーから隠す

このSVGは視覚的な装飾のためのアイコンであり、スクリーンリーダーに読み上げさせる必要はありません。aria-hidden="true" を付けることで、スクリーンリーダーの読み上げ対象から除外しています。

<svg aria-hidden="true">...</svg>

パターン3 — トグルスイッチ

最後は、ON/OFFの切り替えを視覚的に表現できるトグルスイッチ型のデザインです。単一の設定項目を切り替える場面に向いているパターンです。

完成デモの紹介

完成したデモです。実際に操作して動作を確認してみてください。

コード全体(HTML / CSS)

HTMLコード

<div class="cb-toggle-wrap">
  <label>
    <input type="checkbox" name="display" value="darkmode">
    <span class="track"><span class="thumb"></span></span>
    <span class="text">ダークモードを有効にする</span>
  </label>
</div>

CSSコード

.cb-toggle-wrap {
  display: flex;
}

@media (min-width: 768px) {
  .cb-toggle-wrap {
    justify-content: center;
  }
}

.cb-toggle-wrap label {
  display: flex;
  align-items: center;
  gap: 12px;
  position: relative;
  font-size: 18px;
  line-height: 1.5;
  cursor: pointer;
}

.cb-toggle-wrap input[type="checkbox"] {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

.cb-toggle-wrap .track {
  position: relative;
  width: 52px;
  height: 28px;
  border-radius: 14px;
  background: #ccc;
  transition: background-color 300ms ease, box-shadow 300ms ease;
}

.cb-toggle-wrap .thumb {
  position: absolute;
  top: 4px;
  left: 4px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  transition: transform 300ms ease;
}

.cb-toggle-wrap label:hover .track,
.cb-toggle-wrap input:focus-visible + .track {
  box-shadow: 0 0 15px rgba(5, 138, 213, 0.4);
}

.cb-toggle-wrap input:checked + .track {
  background: #058ad5;
}

.cb-toggle-wrap input:checked + .track .thumb {
  transform: translateX(24px);
}

コードのポイント解説

このパターンはパターン1・2とは構造が異なり、複数の選択肢を並べる ul / li は使わず、1つの label の中にトグルUIを組み立てています。ホバー・フォーカス時の光彩表示はパターン1と同じアプローチです。ポイントを2つ解説します。

ポイント1:.trackと.thumbでトグルの見た目を作る

トグルスイッチは、背景の帯(.track)と丸いつまみ(.thumb)の2つの要素で構成しています。.trackborder-radius を高さの半分に指定して楕円形にし、.thumbposition: absolute.track 内に配置しています。topleft で初期位置(OFF状態の位置)を指定しています。

.cb-toggle-wrap .track {
  position: relative;
  width: 52px;
  height: 28px;
  border-radius: 14px;
  background: #ccc;
}

.cb-toggle-wrap .thumb {
  position: absolute;
  top: 4px;
  left: 4px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
}

ポイント2:translateXでつまみをスライドさせる

チェック時は input:checked + .track .thumbtransform: translateX(24px) を指定して、つまみ(.thumb)をON側の位置へ移動させます。transition を指定しておくことで、スライドアニメーションが付与されます。

.cb-toggle-wrap .track {
  transition: background-color 300ms ease, box-shadow 300ms ease;
}

.cb-toggle-wrap .thumb {
  transition: transform 300ms ease;
}

.cb-toggle-wrap input:checked + .track {
  background: #058ad5;
}

.cb-toggle-wrap input:checked + .track .thumb {
  transform: translateX(24px);
}

まとめ

この記事では、CSSでチェックボックスをカスタマイズする方法を3つのパターンで紹介しました。

  • パターン1 — シンプル正方形
    デフォルトに近い構造を保ちながら、見た目をスッキリ整えたいときに
  • パターン2 — カード型
    複数の選択肢を視覚的に強調したいとき、ボタン感のあるUIにしたいときに
  • パターン3 — トグルスイッチ
    単一の設定項目をON/OFFで切り替えたいときに

どのパターンも、JavaScriptなしでCSSだけで実装できます。visually-hiddenやアクセシビリティへの配慮も取り入れているので、そのままプロジェクトに組み込んで使ってみてください。

ラジオボタンのカスタマイズ方法については、以下の記事で詳しく解説しています。あわせてご覧ください。

あわせて読みたい
CSSでラジオボタンをカスタマイズする方法|3パターンのデザインを徹底解説
CSSでラジオボタンをカスタマイズする方法|3パターンのデザインを徹底解説

押していただけると励みになります!

ABOUT ME
みなと
みなと
フロントエンドエンジニア
東京のWeb制作会社で15年以上働いている現役フロントエンドエンジニアです。これまで、いろんなプロジェクトに関わりながら、フロントエンド開発やWebデザインに取り組んできました。このブログでは、今までの経験を活かして、Web制作に役立つ情報やノウハウをシェアしていきたいと思います。初心者の方から、現場で働く方まで、誰でも参考にできる内容をお届けしますので、ぜひ覗いてみてください。
記事URLをコピーしました