CSSで、チェックボックスを好きな見た目にする方法
チェックボックスの色を変えるだけなら、accent-color を1行書けば済みます。枠の形や角の丸み、チェックの形まで変えたいときは、appearance: none でブラウザーの見た目を消し、チェックボックスそのものに枠を描いて、チェックの形を ::before で出します。要素は <input type="checkbox"> のままなので、クリック、Space キー、フォームで送る値は、普通のチェックボックスと変わりません。
検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。HTML と CSS だけのページを Playwright で開き、マウスとキーボードで操作して、getComputedStyle() の値、スクリーンショットの画素の色、アクセシビリティツリーを記録しました。強制カラーモードは Playwright の forcedColors: 'active' で再現したもので、Windows のハイコントラストモードの実機は使っていません。Firefox と Safari、スクリーンリーダーでの実際の読み上げも試していません。各ブラウザーの対応バージョンは、互換性データ(@mdn/browser-compat-data 8.1.8)の値です。

色を変えるだけなら accent-color の1行で済む
.accent {
accent-color: #067a98;
}
accent-color は、チェックボックスなどのフォーム部品の強調色を決めるプロパティです。検証環境で、チェックしたチェックボックスのスクリーンショットの画素を色ごとに数えると、何も指定しないときにいちばん多い色は #0075ff(青)で、accent-color: #067a98 を付けると #067a98 になりました。
チェックの色は、ブラウザーが決めます。#067a98 のような濃い色ではチェックが白になり、#00c8e8 のような明るい色では、チェックが濃い灰色(#3b3b3b)に変わりました。塗りの色が明るくても、チェックが見えなくなることはありませんでした。

大きさは、何も指定しないと 13px 四方でした。width: 22px; height: 22px を付けると 22px 四方になり、accent-color の色もそのまま使われました。色と大きさを変えれば十分なら、ここで終わりです。
互換性データでは、accent-color は Chrome 93、Firefox 92 から使えます。Safari は 15.4 から使えますが、26.2 より前は一部の対応で、「読みやすさのための最低限のコントラストを保たない」という注記が付いています。
appearance: none で、チェックボックスそのものに描き直す
枠を角丸にしたい、チェックの形を変えたい、というときは、ブラウザーの見た目を消して描き直します。HTML は、label の中にチェックボックスと文字を入れるだけです。
<label class="row">
<input class="check" type="checkbox" name="news">
お知らせのメールを受け取る
</label>
.row {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
}
/* 見た目を描き直す:チェックボックスそのものに枠とチェックを描く */
.check {
appearance: none; /* ブラウザーの見た目を消す */
display: inline-grid;
place-content: center;
flex-shrink: 0;
width: 22px;
height: 22px;
margin: 0;
border: 2px solid #067a98;
border-radius: 6px;
background: #fff;
cursor: pointer;
transition: background-color 120ms ease-out;
}
/* チェックの形(はじめは大きさ 0 で見えない) */
.check::before {
content: "";
width: 12px;
height: 12px;
background: #fff;
clip-path: polygon(14% 44%, 0 60%, 40% 100%, 100% 22%, 86% 8%, 38% 70%);
transform: scale(0);
transition: transform 120ms ease-out;
}
/* チェックされたとき:塗りつぶして、チェックを出す */
.check:checked {
background: #067a98;
}
.check:checked::before {
transform: scale(1);
}
MDN の appearance の説明にあるとおり、appearance: none はブラウザー固有の見た目を消すだけで、要素の機能は変わりません。検証環境でも、22px 四方の白い四角に濃い水色の枠が付いた見た目になり、チェックボックスをクリックしたときも、ラベルの文字をクリックしたときも、チェックが切り替わって change イベントが起きました。
チェックの形は、チェックボックスの ::before に 12px 四方の白い四角を置き、clip-path: polygon() でチェックの形に切り抜いて作っています。チェックボックス自体を display: inline-grid と place-content: center にしているので、::before は真ん中に来ます。検証環境の Chromium では、appearance: none のチェックボックスに ::before が描かれました。チェックした後のスクリーンショットでも、塗りの内側に塗りと違う色の画素が 56 個あり、チェックが描かれていることを確かめました。
チェックの出し入れは、::before の大きさを scale(0) と scale(1) で切り替えています。次の表は、transition を一時停止し、時間を 30ms ずつ進めて読んだ値です。
| 経過時間 | チェックの大きさ(倍) | 塗りの色 |
|---|---|---|
| 0ms | 0 | rgb(255, 255, 255) |
| 30ms | 0.38 | rgb(161, 205, 216) |
| 60ms | 0.68 | rgb(85, 164, 184) |
| 90ms | 0.91 | rgb(29, 134, 162) |
| 120ms | 1 | rgb(6, 122, 152) |
チェックが小さい点から大きくなり、塗りの色も同じ 120ms で白から濃い水色に変わりました。
互換性データでは、接頭辞なしの appearance は Chrome 84、Firefox 80、Safari 15.4 から使えます。
未確定の状態は、:indeterminate で横線にする
「すべて選ぶ」のチェックボックスで、一部だけが選ばれている状態を見せたいときは、未確定(indeterminate)の状態を使います。MDN のチェックボックスの説明にあるとおり、この状態は HTML の属性では付けられず、JavaScript で indeterminate プロパティを true にします。
checkbox.indeterminate = true;
MDN のチェックボックスの説明によると、未確定のとき、多くのブラウザーはチェックの代わりに横線を描きます。appearance: none はこうしたブラウザーの見た目を消すので、:indeterminate で自分で描きます。チェックの ::before を高さ 3px にして、clip-path を外すと、白い横線になります。
/* 未確定(indeterminate)のとき:横線を出す */
.check:indeterminate {
background: #067a98;
}
.check:indeterminate::before {
height: 3px;
clip-path: none;
transform: scale(1);
}
検証環境で indeterminate を true にすると、:indeterminate が当てはまり、塗りが濃い水色、::before が 12px × 3px になりました。アクセシビリティツリーには checkbox "すべて選ぶ" [checked=mixed] と出て、見た目を描き直しても、一部だけ選ばれた状態として伝わっています。
未確定の状態は、見た目だけのものです。checked は false のままなので、FormData を読むと、このチェックボックスの値は入っていませんでした。未確定のチェックボックスをクリックすると、indeterminate は false に戻り、checked が true になって、FormData に all=on が入りました。
フォーカスの枠は、チェックボックスそのものに付ける
/* キーボードで選んだときだけ、外側に枠を出す */
.check:focus-visible {
outline: 2px solid #d85878;
outline-offset: 2px;
}
appearance: none で描き直すと、見た目を描く要素と、フォーカスが当たる要素が同じ input になります。そのため、フォーカスの枠も、チェックボックス自身に .check:focus-visible で付けるだけで済み、ほかの要素に枠を移す必要はありません。
検証環境で Tab キーを押してチェックボックスを選ぶと、:focus-visible が当てはまり、チェックボックスの外側に 2px の枠が出ました。そのまま Space キーを押すとオンになって change イベントが起き、Enter キーでは変わりませんでした。マウスでクリックしたときは、:focus-visible は当てはまらず、枠は出ませんでした。

押せないときの見た目も、自分で描く
/* 押せないとき */
.check:disabled {
border-color: #9aa5ab;
background: #eef1f2;
cursor: not-allowed;
}
.check:disabled:checked {
background: #9aa5ab;
}
checked と disabled を付けたチェックボックスは、塗りが灰色(rgb(154, 165, 171))になりました。ラベルをクリックしてもチェックは変わらず、change イベントも起きませんでした。アクセシビリティツリーでは [checked] [disabled] が付きました。
強制カラーモードでは、チェックの色を CanvasText にする
Windows のハイコントラストモードのような強制カラーモードでは、ページの色がシステムの色に置き換わります。MDN の forced-colors の説明によると、background-color や border-color などは、ページで指定した値の代わりに、ブラウザーが選んだシステムの色で描かれます。
チェックの形は ::before の背景色で描いているので、ここが問題になります。Playwright で強制カラーモードを再現すると、塗りの色も ::before の色も白(rgb(255, 255, 255))になりました。オフ、クリックしてオンにしたもの、未確定のどれも、枠の内側に白以外の画素が1つもありませんでした。見た目では、どの状態か分からなくなります。
そこで、強制カラーモードのときだけ、チェックの色をシステムの文字の色(CanvasText)にします。
/* 強制カラーモード:チェックの色をシステムの文字色にする */
@media (forced-colors: active) {
.check::before {
background: CanvasText;
}
}
これを足すと、::before の色は rgb(0, 0, 0) になり、クリックしてオンにしたものではチェックが、未確定のものでは横線が、黒で描かれました。
| 強制カラーモードのとき | オフ | オン | 未確定 |
|---|---|---|---|
| 指定なし | 中は白いまま | 中は白いまま | 中は白いまま |
CanvasText を指定 |
中は白いまま | 黒いチェック | 黒い横線 |

accent-color だけで色を変えたチェックボックスは、強制カラーモードでは濃い紫(#37006e)に塗られ、その中にチェックが描かれていました。チェックが消えたのは、appearance: none で描き直したチェックボックスのほうです。
動きを減らす設定では、transition を外す
チェックが大きくなる動きは、動きを減らす設定のときには止めます。
@media (prefers-reduced-motion: reduce) {
.check,
.check::before {
transition: none;
}
}
Playwright でこの設定にしてクリックすると、チェックはクリックの直後から元の大きさで描かれ、動いているアニメーションは 0 個でした。





