トピックを検索
カードのリンクの中にあり、押すとリンク先も開いてしまうボタン
商品カード全体を1つのリンクで囲み、お気に入りのハートボタンもその中に入れているため、リンクの名前にボタンの言葉が混ざり、ハートを押すと商品ページまで開いてしまう
<a href="/mugs/speckled"> … <button aria-label="お気に入りに追加"> 問題
スクリーンリーダーではカードのリンクがお気に入りの操作まで名乗って読み上げられ、ハートを押すと誰でも商品ページへ移動してしまう
あるお店が、マグカップをカードの一覧で紹介しています。カードには写真、商品名、価格があります。カードのどこを押しても商品ページが開くと押しやすいので、開発者は各カードを1つのリンクで囲みました。写真の隅にある「お気に入りに追加」のハートも、ほかの要素と一緒にリンクの中に入っています。
買い物客がSpeckled mugのハートを押します。ハートは塗りつぶされ、続いて商品ページが開きます。ハートを押すことは、リンクの中を押すことでもあるため、ブラウザーはハートの処理が終わるとリンク先へ移動します。Chrome、Firefox、Safariのエンジンのどれでも同じで、クリックでも、タップでも、EnterやSpaceで押しても変わりません。2つ目のマグを保存するには、一覧へ戻り、さっきの場所を探し直すことになります。
スクリーンリーダーの利用者は、カードの中を1つずつ移動します。最初に止まるのはリンクで、Chromeではその名前が「Speckled mug $24 お気に入りに追加」になります。リンクが、お気に入りの操作まで名乗っています。次に止まるのがハートで、同じリンクの中にある別のボタンです。押したときにどちらが動くのかは、読み上げからは分かりません。実際には、ハートを押すと両方が動きます。
達成基準4.1.2「名前(name)・役割(role)及び値(value)」は、支援技術が各コントロールの種類と働きを伝えられるよう、すべてのコントロールの名前と役割(ロール)をコードから判別できることを求めています。この例では、2つのコントロールが絡み合っています。リンクは中にあるものすべてから名前を作るため、ハートの「お気に入りに追加」がリンクの名前に入り、リンクがしない操作まで読み上げられます。ボタンはリンクの中にあると伝えられるので、押したときに何が起きるかはロールから決まらず、3つのブラウザーエンジンのどれでも両方の動作が起きます。
HTMLはこの書き方を認めていません。a 要素の中には、ボタン、別のリンク、フォームの入力欄などの対話型コンテンツを入れられず、button 要素の中にも入れられません。HTMLパーサーは直せるところを直し、残りはそのままにするため、ページは表示されます。しかし、コントロールの中のコントロールが何を意味するかは、HTMLでは定められていません。今のブラウザーは上で見たとおり同じように動きますが、どう読み上げるかはスクリーンリーダーごとに異なります。WCAG 2.2では、こうしたマークアップを確認していた4.1.1「構文解析」が削除されました。W3Cは削除についての説明で、4.1.1で不適合になっていた問題の多くは、今は1.3.1「情報及び関係性」か4.1.2で不適合になると述べています。リンクの中のボタンもその一つで、理由は上で見た名前とロールへの影響です。
マウスで押すと両方の動作が起きるため、簡単な確認では「動いている」と判断されがちです。問題がはっきり表れるのは、スマートフォンやタッチ画面です。ハートをタップするたびに一覧から移動してしまいます。長い一覧では、戻るたびに同じ場所までスクロールし直すことになります。
10グループ中3グループに影響
- 全盲 (影響あり)
- 弱視 (影響なし)
- 色覚 (影響なし)
- ろう (影響なし)
- 難聴 (影響なし)
- 発話 (影響なし)
- 手の操作 (影響あり)
- 筋力・可動域 (影響なし)
- 認知 (影響あり)
- 光過敏 (影響なし)
- 視覚を使わない カードのリンクの名前が「Speckled mug $24 お気に入りに追加」になるため、スクリーンリーダーはお気に入りの操作まで名乗るリンクを読み上げ、続けてその中のボタンとしてハートを読み上げます。ハートを押すと、マグが保存されたうえで一覧から移動してしまいます。
- 手の操作が限られる キーボードやスイッチの利用者がハートでEnterやSpaceを押すと、商品ページへ移動します。次のマグを保存するには、一覧へ戻って、もう一度順に移動し直す必要があります。
- 言語・認知・学習の能力が限られる あとで見るつもりでハートをタップした人が、求めていないページへ移動してしまいます。マグが保存されたかどうかを確かめ、元の一覧へ戻る道を探すことになります。
axeのルール nested-interactive は、ボタンのように中身ごと1つのものとして読まれるコントロールの中に、フォーカスを受け取る要素がないかを調べます。ボタンの中のリンクは報告されます。リンクの中のボタンは報告されません。ARIAでは、リンクの中身はそれぞれ別の項目として扱われるためです。そのため、このガイドのカードはaxeでは問題なしになります。EaseWebの診断は、リンクの中で見つかったボタン、リンク、入力欄を、人が押して確かめる対象として一覧にします。ハートを押してもページが開かないようにしてほしいとコード生成AIに頼むと、下の応急処置のように、ハートの処理に preventDefault() と stopPropagation() を加えることがあります。ページは開かなくなりますが、ボタンはリンクの中に残ります。
試してみる
まずマウスで、次にTabとEnterで、Speckled mugのハートを押して保存してみましょう
一覧はそのまま残りますか? それともマグのページが開きますか? ブラウザーの戻る矢印で一覧へ戻れます。
問題あり 修正できますか?
修正方法
ハートは、カードのリンクの中ではなく横に置く
<ul class="cards"> <li class="card"> <a href="/mugs/speckled" class="card-link"> <img src="speckled-mug.webp" alt=""> <h3>Speckled mug</h3> <p class="price">$24</p> <button type="button" class="wish" aria-label="お気に入りに追加" aria-pressed="false"> <svg aria-hidden="true">…</svg> </button> </a> </li> …</ul>問題点3点
- ハートの言葉がリンクの名前に入り、リンクは「Speckled mug $24 お気に入りに追加」と読み上げられます。
- マウス、指、Enter、Spaceのどれでハートを押しても、マグが保存されたうえで商品ページが開きます。
- スクリーンリーダーには、HTMLが認めていない「リンクの中のボタン」が渡されるため、どう読み上げるかはスクリーンリーダーごとに異なります。
ハートをリンクの横に置く
<ul class="cards"> <li class="card"> <a href="/mugs/speckled" class="card-link"> <img src="speckled-mug.webp" alt=""> <h3>Speckled mug</h3> <p class="price">$24</p> </a> <button type="button" class="wish" aria-label="お気に入りに追加" aria-pressed="false"> <svg aria-hidden="true">…</svg> </button> </li> …</ul>修正の手順3ステップ
- ボタンを
aの外へ出し、カードの中でリンクの後ろに置きます。リンクが囲むのは写真、商品名、価格になり、名前は「Speckled mug $24」になります。 position: relativeをリンクからカードへ移します。ハートは写真の隅に残り、見た目は元のままです。- ボタンの処理は変えません。ハートを押して動くのはハートだけになり、一覧はそのまま残ります。
これで修正前の例は直り、4.1.2を満たします。ソース上でハートがリンクの後ろにあるため、Tabはリンク、ハートの順に移動し、順序も前と同じです。
wish.addEventListener('click', (event) => { event.preventDefault(); event.stopPropagation(); …});preventDefault() はブラウザーがリンク先へ移動するのを止め、stopPropagation() はクリックがリンク側のルーターの処理へ届くのを止めます。これで、ハートを押してもページは開かなくなります。ほかは何も変わりません。ボタンはリンクの中にあるので、リンクの名前は「Speckled mug $24 お気に入りに追加」のままで、マークアップはHTMLの決まりに反したままです。リンクの中のボタンをどう扱うかも、スクリーンリーダー任せのままです。マウスで操作する人が気づく症状を一つ隠すだけです。
どちらの方法でも基準を満たします。ハートをリンクの外へ出すほうが、変更は小さく済みます。写真、商品名、価格を囲むリンクは1つのまま残り、CSSは position: relative を移すだけで、文字も選択できます。ただし、リンクは中身をすべて読み上げます。商品名と価格だけなら問題ありませんが、評価、色の選択肢、説明文まで並ぶカードでは長くなります。そうしたカードや、カードにコントロールが複数ある場合は、商品名のリンクをカード全体に広げる方法を選んでください。リンクの名前は商品名だけになり、各コントロールはその横に並びます。
同じ入れ子は、手書きのマークアップと同じくらい、コンポーネントからも生まれます。ルーターのリンクコンポーネントでデザインシステムの Button を囲むと、リンクの中にボタンが入ります。1つの操作にTabの停止位置が2つでき、押すと両方が動きます。要素を1つだけ出力してください。多くのライブラリーには、as、component、asChild などのプロパティーでリンクコンポーネントをボタンへ渡す方法があり、1つの要素がリンクのアドレスとボタンの見た目を併せ持てます。ルート要素がリンクのカードコンポーネントや、カードの押せる領域がボタンになっているものも、中に押せるものを入れたとたんに同じ問題が起きます。そうしたコンポーネントでは、操作をカードの押せる領域の外に置いてください。
ハートの aria-pressed はマグが保存済みかどうかを伝え、aria-label はどちらの状態でも同じ名前を保ちます。スクリーンリーダーはトグルボタンとして読み上げ、押されているかどうかを伝えます。この方法と、ほかの選択肢については押された状態が伝わらないトグルボタンを参照してください。
ハートの名前は、どのカードでも「お気に入りに追加」です。スクリーンリーダーの利用者は商品のリンクのすぐ後にハートへ移動するので、位置からどのマグを保存するかは分かります。ただし、ページのボタン一覧では、ハートがすべて同じ名前で並びます。「Speckled mugをお気に入りに追加」のように商品名を加えると、一覧の中でもそれぞれを区別できます。
カード全体で商品ページを開くのは便利さのためで、必須ではありません。商品名だけをリンクにし、カードのほかの部分を押せないままにしても基準を満たし、配置の工夫も要りません。カード全体を押せるようにする場合は、両方の修正と同じように、リンクのフォーカスの枠をカード全体に描き、Enterで何が開くのかをキーボード利用者に見せてください。
修正の確認
4項目
確認方法: キーボード、スクリーンリーダー、マウス、タッチ
OS、ブラウザーと支援技術のバージョン、ビルド、実施日、各手順の実際の結果を記録してください。カードやリンクのコンポーネントを変更した際は、再度確認します。
制限
このガイドが扱うのは、カードのリンクの中に置いたボタンです。逆にボタンの中にリンクを置く形はaxeが報告するもので、直し方は同じく2つを並べて置くことです。言葉のないリンクをバナーやカードに重ねた場合は、名前のないリンクの問題です。名前のないバナーや画像のリンクを参照してください。どのカードも同じリンクの言葉で終わる場合はどれも同じ「続きを読む」のリンク、ホバーでしか現れないカードの操作はホバーでしか現れないカードの操作ボタン、ボタンの働きをするリンクはリンクと読み上げられ、Spaceキーで押せないボタンを参照してください。対象とする支援技術で、操作全体を確認してください。
よくある問題を再現した学習用のサンプルです。実在のクライアントの診断結果ではありません。コードは出発点となる実装例のため、実際の製品でも確認してください。ここでは支援技術でのテスト結果は報告していません。
EaseWebのサービス
修正した問題を、次のリリースで再発させない
このガイドのような修正方法を、コーディングエージェントが従うルールと、CIのチェックに組み込みます。あわせて定期的に、キーボードとスクリーンリーダーで手動の再テストを行います。