# キーボードで操作できないカスタムチェックボックス

Source: https://easeweb.dev/learn/custom-checkboxes
Topics: カスタム入力 > カスタムのチェックボックスとラジオボタンが、ネイティブのinputを残している (WCAG 2.1.1, 4.1.2, 1.3.1)
The fix: 本物のチェックボックスをページとTabの順序に残します。appearance: noneでinput自体にボックスを描くか、描いたボックスを残してinputを見た目だけ隠し、ボックスの上に重ねてinput:checkedとinput:focus-visibleからチェックマークとフォーカスの枠を描くか、ブラウザー標準のボックスを残してaccent-colorで色を付けます。
Test with: keyboard, screen reader, mouse, forced colors, touch
References: https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html https://developer.mozilla.org/en-US/docs/Web/CSS/appearance https://developer.mozilla.org/en-US/docs/Web/CSS/accent-color https://developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors https://www.sarasoueidan.com/blog/inclusively-hiding-and-styling-checkboxes-and-radio-buttons/ https://www.w3.org/WAI/ARIA/apg/patterns/radio/

よくある問題を再現した学習用のサンプルです。実在のクライアントの診断結果ではありません。コードは出発点となる実装例のため、実際の製品でも確認してください。ここでは支援技術でのテスト結果は報告していません。

## 場面

登録フォームのデザインには、独自のチェックボックスが描かれています。ブランドの色である紫の地に白いチェックマークが入る、角の丸いボックスです。ブラウザー標準のボックスはブラウザーごとに見た目が違い、ページのほかの部分とも合わないからです。ボックスは二つあります。「お得な情報をメールで受け取る」と、必須の「利用規約に同意する」で、その下に「アカウントを作成」ボタンがあります。開発者は描かれたとおりに作ります。各ラベルの中に、`display: none` で隠した本物のチェックボックスと、ボックスとして描いた `span` を置き、隠したチェックボックスがチェックされるとその `span` にチェックマークを描きます。ラベルをクリックするとチェックボックスは見えなくてもチェックされるので、どちらの行をクリックしてもチェックが付きます。マウスで使う限り、フォームは正しく見え、正しく動きます。

マウスを使わずに、メールアドレスの欄からTabを押します。フォーカスは「アカウントを作成」へ直接移り、二つのボックスは飛ばされます。Spaceでチェックするものもありません。スクリーンリーダーは「利用規約に同意する」を、チェックボックスも状態もない一行の文として読みます。「アカウントを作成」を押しても何も起きません。規約のボックスは必須なので、ブラウザーはフォームの送信を止めますが、存在しないボックスにはフォーカスを移せません。Chromeはメッセージも出さず、開発者ツールのコンソールに「An invalid form control with name='terms' is not focusable.」と1行出るだけです。キーボードの利用者は、アカウントをまったく作れません。

## 確認できる問題

`display: none` は、チェックボックスをページから完全に取り除きます。Tabの順序からも、アクセシビリティツリーからも外れ、画面に残るのはチェックボックスの絵だけです。マウスはラベルを通じてその機能に届きますが、キーボードは届かないため、達成基準2.1.1 キーボードを満たしません。また、チェックボックスに見えるものが、名前、役割、チェックの状態を持つチェックボックスとして公開されていないため、4.1.2 名前・役割・値も満たしません。その行がチェックできる選択肢であることが絵でしか示されず、コードにないため、1.3.1 情報及び関係性も満たしません。

同じデザインは、ほかの作り方でも同じように失敗します。

- チェックボックスを `visibility: hidden` や `hidden` 属性で隠す。どちらもTabの順序とアクセシビリティツリーから外れ、ラベルをクリックすればチェックは付きます。
- inputを使わず、クリックのハンドラーを付けた `div` をチェックボックスとして描く。キーボードからは届かず、何も読み上げられず、スクリプトで加えない限りフォームと一緒に送信もされません。
- 配送方法などのラジオボタンを、ラジオボタンを隠した選択肢のカードとして描く。Tabに加えて、グループの中で選択を移す矢印キーも使えなくなります。

## 利用者への影響

規約への同意、年齢の確認、配送方法の選択など、必須の選択は登録、購入手続き、設定のページにあります。マウスでしかチェックできないと、フォームはそのボックスで終わり、画面にはその理由が何も示されません。

## 自動チェックとAIの限界

隠れたinputは、ほとんどのチェックの対象外です。axeは表示されているものを調べるので、`display: none` のチェックボックスは調べず、横のラベルはただの文で、問題はありません。失敗は欠けているものにあり、あるものを調べても何も見つかりません。EaseWebの診断は、ラベルが表示されているのに隠れているチェックボックスやラジオボタンを `ew:hidden-native-input` として報告します。修正を頼むと、コード生成AIはラベルや描いたボックスに `tabindex="0"` を付けることがあります。Tabはそこに止まるようになりますが、ラベルはSpaceでチェックされず、チェックボックスも状態も読み上げられません。`display: none` を `visibility: hidden` に変えても何も変わりません。マウスを使わずに、フォームを試してください。

## 修正前：display: noneで隠したチェックボックス

```html
<form action="/sign-up" method="post">
  …
  <label class="check">
    <input type="checkbox" name="offers">
    <span class="box"></span>
    お得な情報をメールで受け取る
  </label>
  <label class="check">
    <input type="checkbox" name="terms" required>
    <span class="box"></span>
    利用規約に同意する
  </label>
  <button type="submit">アカウントを作成</button>
</form>
```

```css
.check {
  display: flex;
  align-items: center;
  gap: 0.625rem;
}
.check input {
  display: none;
}
/* 隠したチェックボックスの後に描くボックス */
.check .box {
  display: inline-grid;
  place-content: center;
  box-sizing: border-box;
  inline-size: 1.25rem;
  block-size: 1.25rem;
  border: 2px solid #6b7280;
  border-radius: 0.375rem;
}
/* チェックマーク：回転させた箱の2辺の枠線 */
.check .box::before {
  content: "";
  inline-size: 0.375rem;
  block-size: 0.625rem;
  border: solid #fff;
  border-width: 0 3px 3px 0;
  rotate: 45deg;
  visibility: hidden;
}
.check input:checked + .box {
  border-color: #4f46e5;
  background: #4f46e5;
}
.check input:checked + .box::before {
  visibility: visible;
}
```

ラベルをクリックすると隠したチェックボックスがチェックされ、ボックスにチェックマークが描かれます。しかし `display: none` のため、チェックボックスはTabの順序にもアクセシビリティツリーにもありません。キーボードは届かず、スクリーンリーダーはその存在を知りません。

## 修正後：input自体に描いたボックス

```html
<form action="/sign-up" method="post">
  …
  <label class="check">
    <input type="checkbox" name="offers">
    お得な情報をメールで受け取る
  </label>
  <label class="check">
    <input type="checkbox" name="terms" required>
    利用規約に同意する
  </label>
  <button type="submit">アカウントを作成</button>
</form>
```

```css
.check {
  display: flex;
  align-items: center;
  gap: 0.625rem;
}
/* チェックボックス自体に描くボックス */
.check input {
  appearance: none;
  display: inline-grid;
  place-content: center;
  margin: 0;
  inline-size: 1.25rem;
  block-size: 1.25rem;
  border: 2px solid #6b7280;
  border-radius: 0.375rem;
}
/* チェックマーク：回転させた箱の2辺の枠線 */
.check input::before {
  content: "";
  inline-size: 0.375rem;
  block-size: 0.625rem;
  border: solid #fff;
  border-width: 0 3px 3px 0;
  rotate: 45deg;
  visibility: hidden;
}
.check input:checked {
  border-color: #4f46e5;
  background: #4f46e5;
}
.check input:checked::before {
  visibility: visible;
}
.check input:focus-visible {
  outline: 2px solid #4f46e5;
  outline-offset: 2px;
}
```

`appearance: none` はブラウザーの描画だけを取り除き、チェックボックス自体は残すので、CSSで同じ角の丸いボックスとチェックマークをその上に描けます。何も隠していないため、Tabはボックスに止まり、Spaceでチェックでき、スクリーンリーダーは「利用規約に同意する、チェックボックス、チェックなし、必須」のように読み上げ、ブラウザーは必須であることを伝えるためにボックスへフォーカスを移せます。チェックマークと枠はチェックボックス自体の `:checked` と `:focus-visible` から描くので、見た目と読み上げがずれません。

## 修正後：描いたボックスを残し、inputを上に重ねる

```html
<form action="/sign-up" method="post">
  …
  <label class="check">
    <input type="checkbox" name="offers">
    <span class="box"></span>
    お得な情報をメールで受け取る
  </label>
  <label class="check">
    <input type="checkbox" name="terms" required>
    <span class="box"></span>
    利用規約に同意する
  </label>
  <button type="submit">アカウントを作成</button>
</form>
```

```css
.check {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.625rem;
}
/* 見た目だけ隠す：ボックスと同じ大きさで、ボックスの上に重ねる */
.check input {
  position: absolute;
  inline-size: 1.25rem;
  block-size: 1.25rem;
  margin: 0;
  opacity: 0;
}
/* チェックボックスの後に描くボックス */
.check .box {
  display: inline-grid;
  place-content: center;
  box-sizing: border-box;
  inline-size: 1.25rem;
  block-size: 1.25rem;
  border: 2px solid #6b7280;
  border-radius: 0.375rem;
}
/* チェックマーク：回転させた箱の2辺の枠線 */
.check .box::before {
  content: "";
  inline-size: 0.375rem;
  block-size: 0.625rem;
  border: solid #fff;
  border-width: 0 3px 3px 0;
  rotate: 45deg;
  visibility: hidden;
}
.check input:checked + .box {
  border-color: #4f46e5;
  background: #4f46e5;
}
.check input:checked + .box::before {
  visibility: visible;
}
.check input:focus-visible + .box {
  outline: 2px solid #4f46e5;
  outline-offset: 2px;
}
```

マークアップは変わりません。チェックボックスは取り除かれずに透明になるだけなので、ページ、Tabの順序、アクセシビリティツリーに残り、その後のボックスが状態とフォーカスを示します。この方法は、コードが守っている二つのことに頼っています。一つは、ボックスがチェックボックスのすぐ次の兄弟要素であることです。そうでないと `+ .box` が何にも一致せず、ボックスにチェックマークもフォーカスも出ません。もう一つは、チェックボックスがボックスと同じ大きさでその上にあることです。ここでは位置を指定せずに絶対配置しているので、ラベルの先頭、つまりボックスの位置にとどまります。スマートフォンのスクリーンリーダーは画面上の位置で要素を探すため、1ピクセルに縮めたり画面外へ移したりしたチェックボックスは、利用者が触れたボックスとは別の場所で見つかります。

## 修正後：ブランドの色にしたブラウザー標準のボックス

```html
<form action="/sign-up" method="post">
  …
  <label class="check">
    <input type="checkbox" name="offers">
    お得な情報をメールで受け取る
  </label>
  <label class="check">
    <input type="checkbox" name="terms" required>
    利用規約に同意する
  </label>
  <button type="submit">アカウントを作成</button>
</form>
```

```css
.check {
  display: flex;
  align-items: center;
  gap: 0.625rem;
}
/* ブラウザー標準のボックスを、ブランドの色で */
.check input {
  margin: 0;
  inline-size: 1.25rem;
  block-size: 1.25rem;
  accent-color: #4f46e5;
}
```

最も小さな変更です。描いたボックスを削除し、ブラウザーのチェックボックスに `accent-color` で色を付けます。ボックス、チェックマーク、フォーカスの枠、コントラスト テーマでの表示はブラウザーが描くので、保守するものなしに、ここで扱うすべてを満たします。ただし見た目は変わります。自分で決められるのは色だけです。形、角の丸み、枠線、チェックマークはブラウザーのもので、Chrome、Firefox、Safariで、またOSによっても異なります。チェックマークも、指定した色に対して見分けられるようブラウザーが調整します。ただし、Android版ChromeとSafari 26.2より前のバージョンは調整しません。

## どの方法を選ぶか

三つとも適合します。特別な理由がなければ、input自体にボックスを描きます。要素が一つなので何も隠さず、ボックスの上に置いたり横に並べ続けたりする別の要素もなく、ブラウザーのフォーカス、タッチでの位置、フォームの動作が、目に見えるボックスと一緒にあります。ほぼすべての独自のチェックボックスでこれを勧める理由はここにあります。アイコンやSVGで描いたチェックマークなど、inputには入れられないものをボックスに入れる場合や、コンポーネントのマークアップを変えられない場合は、描いたボックスを残してinputを上に重ねます。その場合は、ボックスをチェックボックスのすぐ後に置き、チェックボックスをボックスの上に置き続けます。ブランドに必要なのがボックスの色だけの場合や、大きなサイトの最初の一歩としては `accent-color` を使います。問題を一度にすべての場所から取り除け、描いたボックスは後から加えられます。

## 実装の判断

ラジオボタンにも同じ三つの修正が使えます。`appearance: none` と `border-radius: 50%` でinputに円を描いて点を `:checked` から描くか、見た目だけ隠したラジオボタンの後に描いた円やカードを置くか、ブラウザーのラジオボタンに `accent-color` で色を付けます。各グループを `legend` のある `fieldset` に入れ、ラジオボタンに同じ `name` を付けます。そうするとTabはグループに一度だけ止まり、矢印キーで選択が移ります。これが、隠したラジオボタンのグループで失われるものです。選択肢のカードは、カード全体をラベルの中に入れてどこをクリックしても選べるようにし、ラジオボタンの円をカードの中に見えるように残します。

Windowsのコントラスト テーマでは、ブラウザーが塗りをページの背景色に置き換え、影を取り除きます。ここでのチェックマークは枠線で描いており、枠線はブラウザーが利用者の配色の色で描き直すため、紫の塗りが消えても、チェックしたボックスにはチェックマークが残ります。`background-color` と `clip-path` やマスクで塗りとして描いたチェックマークや点は、塗りと一緒に消えます。`@media (forced-colors: active)` の中で `background-color: CanvasText` を指定してください。同じ理由で、フォーカスの枠は `box-shadow` ではなく `outline` で描きます。

`required` は本物のチェックボックスに残します。チェックボックスがページにあれば、フォームの送信時にブラウザーがそこへフォーカスを移し、何が足りないかを伝えられます。隠してあると、メッセージを出す場所がありません。フォーム独自のエラーメッセージを出す場合は、描いたボックスではなくチェックボックスに関連付けます。

コンポーネントライブラリやテーマには、隠したinputが含まれていることがよくあります。コンポーネントの名前ではなく、出力されたマークアップを確認してください。スタイルを付けたラベルの横に `display: none` や `hidden` のチェックボックスがあれば、名前が何であれ失敗です。隠したチェックボックスとラベルで開くメニュー、CSSだけで作る「チェックボックスハック」も同じように失敗します。[開いているか伝わらない表示・非表示ボタン](/ja/learn/show-hide-buttons)で説明しているように、開いているかを伝えるボタンで作ります。

## 修正の確認

1. マウスを使わずに、フォームをTabで移動する（MacのSafariでは、先に「詳細」設定で、Tabキーで各項目を強調表示する設定をオンにする。オフのままだと、Tabはどのサイトでもチェックボックスを飛ばす）。表示の順に、すべてのチェックボックスと各ラジオボタンのグループに一度ずつ、見えるフォーカスの枠とともに到達できること、Spaceで各ボックスのチェックとその解除ができること、ラジオボタンのグループでは矢印キーで選択が移ることを確認する。
2. 必須のボックスを空のまま、キーボードでフォームを送信し、フォーカスがボックスへ移ってメッセージが出ることを確認する。Spaceでチェックしてもう一度送信し、送信されることを確認する。
3. スクリーンリーダーでフォームを移動し、各ボックスがラベル、チェックボックスまたはラジオボタン、状態とともに読み上げられ、規約のボックスは必須と読み上げられ、状態の変化が読み上げられることを確認する。
4. Windowsのコントラスト テーマ、または強制配色のエミュレーションで、チェックしたボックスと空のボックス、選んだラジオボタンとほかのラジオボタンを見分けられ、フォーカスの枠が見えることを確認する。
5. スマートフォンでスクリーンリーダーを有効にし、描かれた各ボックスに触れて、そこでチェックボックスが見つかり、ダブルタップでチェックできることを確認する。

OS、ブラウザーと支援技術のバージョン、ビルド、実施日、各手順の実際の結果を記録してください。共通のフォームコンポーネントやテーマを変更した際は、再度確認します。

## 制限

このサンプルは、どのアプリケーションでも同じ結果になることを保証するものではありません。CSSは最小限の例なので、ボックスの大きさ、枠線とフォーカスの枠のコントラストを実際の配色で確認してください。ARIAで作り直して状態を誤って伝えるチェックボックスは別の問題で、[読み上げを誤らせる、無効なARIA](/ja/learn/valid-aria)で扱います。設問が結び付いていない選択肢のグループは[設問名がないラジオボタンとチェックボックスのグループ](/ja/learn/field-groups)で、スイッチとして描いたチェックボックスは[オンかオフかが伝わらないトグルスイッチ](/ja/learn/toggle-switches)で扱います。キーボード、スクリーンリーダー、コントラスト テーマで、フォーム全体を確認してください。

---

このガイドをコーディングエージェントで使う: MCP https://easeweb.dev/mcp（設定は https://easeweb.dev/ja/services/developers）
