トピックを検索
idの重複でラベルが別の欄を指してしまうフォーム
ページの中ほどとフッターに同じニュースレターのフォームをidごと置いているため、フッターのラベルが最初のフォームの入力欄を指し、クリックするとそちらへ移ってしまう
<input id="email" name="email" type="email"> 問題
スクリーンリーダーではフッターのメール欄のラベルが読み上げられず、そのラベルをクリックすると、カーソルがページの上のほうにある別のフォームへ戻ってしまう
紅茶のお店のトップページでは、ニュースレターへの登録を2か所で呼びかけています。ページの中ほどにある「ニュースレターに登録」の帯と、多くの人が探すフッターです。どちらも「メールアドレス」というラベル、入力欄、「登録する」ボタンだけの同じ小さなフォームで、1つの部品として書き、両方の場所で読み込んでいます。保守するフォームが1つで済むので、作りとしては理にかなっています。ところが、この部品は入力欄に id="email"、ラベルに for="email" を書き出すため、ページの中に同じidを持つ要素が2つできます。
スクリーンリーダーの利用者がフッターまで進み、Tabで入力欄に入ると、「編集テキスト」とだけ聞こえ、何を入力するのかが分かりません。ページ中ほどの欄に戻ると、今度は「メールアドレス メールアドレス」と読み上げられます。目で見て操作する人が、欄にカーソルを入れようとしてフッターの「メールアドレス」という文字をクリックすると、ページが中ほどの帯までスクロールし、カーソルは帯の欄に入ります。もう一度下までスクロールして欄そのものをクリックすれば、入力はできます。フッターのフォームでも、入力と送信はできます。壊れているのは、各ラベルと入力欄の結び付きです。
HTMLがidを文書の中で一意にするよう求めている理由が、ここに表れています。ラベルの for は、そのidを持ついちばん近い欄を探すのではなく、ページの中でそのidを持つ最初の要素を指します。そのため、2つのラベルはどちらも帯の欄を指し、帯の欄にはラベルが2つ、フッターの欄には1つもない状態になります。aria-labelledby、aria-describedby、aria-controls も同じく、各idを持つ最初の要素を指します。繰り返す部品の中のヒントやエラーメッセージが別のコピーで読み上げられてしまうのも、このためです。
達成基準1.3.1「情報及び関係性」は、画面で示す関係をコードからも判別できることを求めています。画面では、フッターの「メールアドレス」はフッターの欄の上にありますが、コード上はページの上のほうにある別の欄のものです。達成基準4.1.2「名前(name)・役割(role)及び値(value)」は、すべてのコントロールの名前をコードから判別できることを求めていますが、フッターの欄には名前がまったくありません。ChromeとFirefoxはどちらも、帯の欄を「メールアドレス メールアドレス」、フッターの欄を名前なしとして扱います。WCAG 2.2では、idの重複そのものを不適合としていた達成基準4.1.1「構文解析」が削除されました。その解説ページは、こうした問題の多くが「情報及び関係性」か「名前・役割及び値」に適合しなくなると説明しています。この例もその1つで、idの重複が原因、ラベルがないことが不適合です。
ラベルをクリックすれば、障害の有無にかかわらず、誰でもページが飛んでしまいます。フッターの欄にプレースホルダーがあると、ラベルがないことが見えにくくなります。ほかに名前がないとき、ブラウザーはプレースホルダーを名前として使うため、スクリーンリーダーはそれを読み上げ、問題が分かるのはクリックしたときだけになります。
10グループ中4グループに影響
- 全盲 (影響あり)
- 弱視 (影響あり)
- 色覚 (影響なし)
- ろう (影響なし)
- 難聴 (影響なし)
- 発話 (影響なし)
- 手の操作 (影響あり)
- 筋力・可動域 (影響あり)
- 認知 (影響なし)
- 光過敏 (影響なし)
- 視覚を使わない フッターの欄では、スクリーンリーダーが「編集テキスト」とだけ読み上げ、何を入力するのか分かりません。ページ中ほどの欄では、2つのラベルがどちらもそこを指すため「メールアドレス メールアドレス」と読み上げます。
- 手の操作が限られる 音声操作の利用者が「メールアドレスをクリック」と言っても、フッターの欄には届きません。その欄には名前がなく、名前に「メールアドレス」を含む欄は、ページの上のほうにある1つだけだからです。
- 視覚が限られる 拡大鏡を使う人は、ラベルをクリックして欄にカーソルを入れることがよくあります。フッターのラベルではカーソルもページもページ中ほどへ移るため、見ていた場所を見失い、入力した内容は別のフォームに入ります。
- 筋力や可動域が限られる 手にふるえのある人は、欄そのものよりラベルの文字をクリックすることがよくあります。ここではクリックするとページの上へ移ってしまい、フッターまで戻る手間がかかります。
axeのlabelルールは、入力欄のidを指すラベルがあるかを確認します。フッターの欄にはそのようなラベルがあるので、ルールは合格とします。axeはidの重複にも気づきますが、duplicate-id-ariaルールは常に確認候補として出すだけです。idが重複しているだけでは、ラベルがどのコピー向けに書かれたのかが分からないからです。form-field-multiple-labelsルールも、ラベルが複数あるとして、両方の欄を確認候補にします。EaseWebの診断は、同じidを持つ欄を2つ以上のラベルが指し、後ろの欄にラベルがない場合に、フッターの欄を問題として報告します。aria-labelledby や aria-describedby だけが使う重複したidは、人が確かめる対象として残します。部品を1回だけ表示するテストでは、この問題は起きません。コード生成AIにニュースレターのフォームを頼むと、固定のidを書くことがあります。フォームを2か所目で使うまでは、それで正しく動きます。
試してみる
フッターまでスクロールして「メールアドレス」という文字をクリックし、Tabで2つのメール欄に順に入ってみましょう
クリックすると、カーソルはどこに入りますか? 下の欄に、それぞれのメール欄でスクリーンリーダーが読み上げる内容が出ます。
問題あり 修正できますか?
少量ずつブレンドした茶葉
ニュースレターに登録
新しい紅茶とお得な情報を、月に1回お届けします。
今月の紅茶
- モーニング・アッサム $14
- スモーク・ラプサン $16
- ミント&ネトル $12
どの紅茶も密閉できる缶でお届けし、40ドル以上のご注文は送料無料です。
スクリーンリーダーの読み上げ:
(Tabでメール欄へ移動)
修正方法
コピーごとに別のidを付けるか、入力欄をラベルで囲む
<section class="signup"> <h2>ニュースレターに登録</h2> <form class="signup-form" action="/subscribe" method="post"> <label for="email">メールアドレス</label> <input id="email" name="email" type="email" autocomplete="email"> <button>登録する</button> </form></section>…<footer> … <form class="signup-form" action="/subscribe" method="post"> <label for="email">メールアドレス</label> <input id="email" name="email" type="email" autocomplete="email"> <button>登録する</button> </form></footer>問題点3点
- 2つの入力欄が
id="email"を持つため、2つのラベルはどちらも最初の欄、つまり帯の欄を指します。 - フッターの欄にはラベルがありません。スクリーンリーダーはこの欄を「編集テキスト」、帯の欄を「メールアドレス メールアドレス」と読み上げます。
- フッターの「メールアドレス」をクリックすると、帯の欄にカーソルが入り、ページがそこまでスクロールします。
コピーごとにidを付ける
<section class="signup"> <h2>ニュースレターに登録</h2> <form class="signup-form" action="/subscribe" method="post"> <label for="email-signup">メールアドレス</label> <input id="email-signup" name="email" type="email" autocomplete="email"> <button>登録する</button> </form></section>…<footer> … <form class="signup-form" action="/subscribe" method="post"> <label for="email-footer">メールアドレス</label> <input id="email-footer" name="email" type="email" autocomplete="email"> <button>登録する</button> </form></footer>修正の手順3ステップ
- 部品を読み込む場所ごとに
signup、footerのような値を渡し、部品が書き出すすべてのidに付けます。これで、入力欄はそれぞれ自分のidを持ちます。 - 各ラベルの
forを、同じコピーのidに向けます。スクリーンリーダーはどちらの欄も「メールアドレス」と読み上げ、どちらのラベルをクリックしても、その下の欄にカーソルが入ります。 name="email"はそのままにします。nameはサーバーが受け取る名前で、重複してもかまいません。一意でなければならないのはidだけです。
これで修正前の例は直り、1.3.1と4.1.2を満たします。コンポーネントのフレームワークでは、値を渡す代わりにフレームワークにidを作らせます。ReactとVueでは useId()、Svelteでは $props.id()、Shopifyのテーマでは、JSONテンプレートから追加したセクションの section.id です(同じ静的セクションを2回描くと、ファイル名がidとして重複します)。一覧の中では、商品のidや一覧での順番など、項目ごとに異なる値からidを作ります。
どちらの方法でも基準を満たします。コピーごとにidを付ける方法は、マークアップもレイアウトも変えずに済みます。欄にヒントやエラーメッセージなど、idで指すものがほかにもある場合は、どのみち一意のidが必要なので、こちらを使います。入力欄をラベルの中に入れる方法は、重複するものも、そろえておくものも残りません。ラベル以外に欄を指すもののない、この例のような小さなフォームに向いています。代わりにCSSの変更が必要です。ラベルで欄を囲み、それ以外のものには生成したidを使うというように、両方を組み合わせることもできます。
同じフォームを2か所に置く場合のほかに、ループの中で使う部品でも起きます。かごの商品ごとにある「ギフト包装」の選択肢を id="gift-wrap" で書くと、すべての商品のチェックボックスが同じidになり、どの商品の「ギフト包装」をクリックしても、最初の商品のチェックボックスにチェックが入ります。ページで繰り返し表示する部品は、どれも同じように確認が必要です。
- スマートフォン用と広い画面用に、ページが2回出力するダイアログやドロワー
- それぞれ
aria-controlsで自分のパネルを指す、スマートフォン用とパソコン用の同じメニュー - サービスの埋め込みコードを2回貼り付けたウィジェット。idはコードの中に固定で書かれている(Mailchimpの埋め込みフォームは
id="mce-EMAIL"を書き出す)
片方のコピーをCSSで隠しても解決しません。display: none で隠したコピーもページの中にあり、それが先にあればラベルはそちらを指します。すると、表示しているコピーのほうにラベルがなくなり、そのラベルをクリックしても何も起きません。表示するコピーだけを出力するか、コピーごとにidを付けてください。
乱数やカウンターで作ったidは、サーバーでの描画とブラウザーでの描画とで値が食い違うことがあり、両方で描画するフレームワークではハイドレーションのエラーになります。React、Vue、Svelteが独自のid関数を用意しているのはそのためです。Math.random() ではなく、それらを使ってください。
2つのフォームに同じ aria-label を付けると、同じように聞こえるフォームのランドマークが2つできますが、これは別の問題です。繰り返す領域の名前の付け方は、区別できない複数のナビゲーションランドマークを参照してください。テンプレートで名前を変えたidなど、存在しないidを for が指すラベルについてはラベルがない・表示と食い違うフォームの入力欄を、存在しないidを指す aria-labelledby や aria-describedby については読み上げを誤らせる、無効なARIAを参照してください。
修正の確認
4項目
確認方法: スクリーンリーダー、マウス
OS、ブラウザーと支援技術のバージョン、ビルド、実施日、各手順の実際の結果を記録してください。共通の部品を新しい場所で使ったときは、再度確認します。
制限
このガイドが扱うのは、2回以上表示する部品が繰り返すidと、それがラベルに与える影響です。別々の要素に手作業でたまたま同じidを付けた場合(片方の名前を変えれば直る)や、存在しないidを指す場合は扱いません。繰り返すフォームの見えるラベルを、場所ごとに「ニュースレター用のメールアドレス」のように変えるべきかどうかは、何を入力するのか分からない、あいまいなフォームのラベルで扱います。対象とする支援技術で、操作全体を確認してください。
よくある問題を再現した学習用のサンプルです。実在のクライアントの診断結果ではありません。コードは出発点となる実装例のため、実際の製品でも確認してください。ここでは支援技術でのテスト結果は報告していません。
参考資料
- WCAG Understanding: info-and-relationships(新しいタブで開きます)
- WCAG Understanding: name-role-value(新しいタブで開きます)
- WCAG Understanding: parsing(新しいタブで開きます)
- html.spec.whatwg.org: forms(新しいタブで開きます)
- html.spec.whatwg.org: dom(新しいタブで開きます)
- developer.mozilla.org: label(新しいタブで開きます)
- react.dev: useId(新しいタブで開きます)
- dequeuniversity.com: duplicate-id-aria(新しいタブで開きます)
EaseWebのサービス
修正した問題を、次のリリースで再発させない
このガイドのような修正方法を、コーディングエージェントが従うルールと、CIのチェックに組み込みます。あわせて定期的に、キーボードとスクリーンリーダーで手動の再テストを行います。