WCAG 達成基準 · Level A

WCAG 4.1.1: 構文解析(WCAG 2.2 で非推奨)

WCAG 4.1.1 の「構文解析」では、支援技術がページを誤って解釈したり処理に失敗したりする原因となる、重複した ID などの重大な HTML/XML エラーがウェブコンテンツに存在しないことが求められます。WCAG 2.2 ではこの達成基準は非推奨とされていますが、その基盤となる axe-core のルールは依然として有効であり、違反は依然として実際のアクセシビリティ上のリスクを示しています。

このルールが意味すること

WCAG 4.1.1 Parsing は、もともとブラウザや支援技術を含むユーザーエージェントが、ウェブコンテンツを正確にパースして解釈できるようにするために設計されました。この達成基準では、HTML や XML などのマークアップ言語で作成されたページが、次の4つの構造条件を満たすことを求めていました。要素には開始タグと終了タグが揃っていること、要素は仕様どおりに入れ子になっていること、要素に重複した属性が含まれていないこと、そしてコンテンツ内で使用されるあらゆる ID が一意であること、の4点です。

WCAG 2.2 では、W3C はこの達成基準を正式に廃止しました。その理由は、現代のブラウザが不正な HTML に対して非常に強靭になり、多くの構造的なエラーをアクセシビリティツリーに到達する前に自動修正するようになったためです。その結果、未閉じのタグや不適切な入れ子構造といった当初の懸念の多くは、現代の環境において支援技術に実質的な害を与えることがなくなりました。

しかし、廃止されたからといって、この達成基準が扱っていた懸念が完全になくなったわけではありません。W3C は、重複した ID 属性が依然として意味のあるアクセシビリティ上の問題であると明示しています。2つ以上の要素が同じ id 値を共有している場合、ブラウザは ARIA 参照、ラベルの関連付け、フラグメントリンクをどの要素に結び付けるかについて、恣意的な判断を下さなければなりません。この曖昧さにより、スクリーンリーダーが誤ったコンテンツを読み上げたり、インタラクティブなコントロールを飛ばしてしまったり、フォームラベルをまったく公開しないといった事態が起こり得ます。したがって、この達成基準の合格条件は、今日では「DOM に重複した ID 値が存在しないこと」と理解するのが最も適切です。支援技術が依存するプログラムによる関連付けを破壊するような形で ID が重複している場合、そのページはこの達成基準に不合格となります。

WCAG 仕様における公式な例外は最小限です。この達成基準はマークアップ言語で作成されたコンテンツに適用され、著者が出力形式を直接制御できないスクリプト環境によって生成されるコンテンツには適用されません。しかし実務上は、どのような技術スタックを用いて生成したとしても、開発者は最終的にレンダリングされる DOM に対して責任を負います。

なぜ重要か

重複した ID は一見すると些細な整理整頓の問題のように見えますが、支援技術ユーザーにとっては深刻な結果を招くことがあります。JAWS、NVDA、VoiceOver といったスクリーンリーダーはブラウザのアクセシビリティツリーに依存しており、そのツリーはインターフェイス要素間の関係を構築するために、正しく解決された ID 参照に依存しています。ID が重複している場合、ブラウザは通常、文書順で最初の一致要素にのみ参照を解決し、同じ ID を持つ後続の要素は黙って無視します。

視覚障害者およびロービジョンのユーザーにとっては、フォームフィールドがラベルなしで読み上げられたり、aria-describedby で入力欄に関連付けられたエラーメッセージが読み上げられないといった事態を意味します。たとえば、EC サイトのチェックアウトフォームで、配送先住所欄と請求先住所欄の両方が cityzipstate のような ID を使用しているケースを考えてみてください。スクリーンリーダーユーザーが請求先セクションを入力しているときに、配送先セクションのラベルが読み上げられてしまい、混乱や入力ミス、取引の放棄につながる可能性があります。

認知障害のあるユーザーにとっては、ラベルの関連付けが壊れていると、画面上で目にしているテキストと、スクリーンリーダーや音声コントロールソフトウェアが読み上げる内容が一致せず、認知負荷を高める混乱した断絶が生じます。

Dragon NaturallySpeaking のような音声入力ソフトウェアに依存する運動障害のあるユーザーにとっては、重複した ID により、特定のコントロールを対象とした音声コマンドが誤った要素をアクティブにしてしまうことがあります。これは、ソフトウェアが内部的に ID ベースのターゲティングに依存している場合に起こり得ます。

障害への影響にとどまらず、重複した ID はSEOにも影響します。特定のページセクションをインデックスするためにフラグメント識別子に依存する検索エンジンクローラーは、ID が一意でない場合に誤ったコンテンツをインデックスしてしまう可能性があります。また、ページ内アンカーリンクがページ上の誤った位置に移動してしまうため、すべてのユーザーにとってユーザビリティも低下します。

世界保健機関によると、世界で22億人が何らかの視覚障害を抱えています。そのうち相当数のユーザーが、ID の関連付けの破綻によって直接影響を受けるスクリーンリーダーに依存しています。ID を一意に保つことは、開発チームが実施できる対策の中でも、労力が最も少なく、効果が最も高いもののひとつです。

関連する Axe-core のルール

3つの axe-core ルールが、WCAG 4.1.1 によって提起された懸念に直接対応しています。それぞれが、重複した ID 問題の特定の現れ方を対象としています。

  • duplicate-id: このルールは DOM 全体をチェックし、複数の要素に現れるあらゆる id 属性値を検出します。ARIA によって参照されているかどうか、あるいはその要素がインタラクティブかどうかに関係なく、同じ ID を共有する最初の要素以外のすべての要素をフラグします。これは3つのルールの中で最も包括的なものであり、明示的な ARIA 関係が存在しない場合でも構造上の違反を検出します。よくあるトリガーは、同じ再利用コンポーネントをページ上に複数回レンダリングしながら、各インスタンスに一意の ID を生成していないコンポーネントベースのフレームワークです。
  • duplicate-id-active: このルールは、ボタン、リンク、入力欄、非負の tabindex を持つ要素など、インタラクティブまたはフォーカス可能な要素上の重複 ID に対象を絞ります。ここでは、支援技術とキーボードナビゲーションの両方がアクティブなコントロールを一意に識別できることに依存しているため、アクセシビリティへの影響はより大きくなります。送信ボタンと無関係なアイコンが同じ ID を共有していると、タブ順の読み上げやプログラムによるフォーカス管理の両方が破綻する可能性があります。
  • duplicate-id-aria: これは3つのルールの中で最も重要なものです。このルールは、aria-labelledbyaria-describedbyaria-controlsaria-owns などの関係属性によって参照されている ID に重複がある場合に特にフラグを立てます。これらの属性は、支援技術が要素間の関係を理解するための主要なメカニズムであるため、ここでの重複はアクセシブルネームの計算やロール関係を直接破壊します。失敗例としては、モーダルが aria-labelledby='dialog-title' を使用しているにもかかわらず、id='dialog-title' を持つ <div> 要素が2つ存在するケースが挙げられます。この場合、スクリーンリーダーは DOM 上で先に現れる要素を読み上げますが、それが意図したダイアログ見出しでない可能性があります。

自動化ツールは、重複した ID を検出するのに非常に適しています。チェックは純粋に構文的なものであり、ツールは DOM を読み取り ID 値を比較するだけで済むからです。この達成基準については、厳密には手動テストは不要です。ただし、ユーザーの操作後に ID が動的に生成される場合、たとえば無限スクロールで新しいコンテンツが重複した ID とともに挿入されるようなケースでは、ページロード時に実行した自動スキャンでは、後から現れる違反を見逃す可能性があります。そのような場合、テスターはスキャンを実行する前に動的な挙動をトリガーするか、操作後にブラウザの開発者ツールを使って DOM を監視する必要があります。

テスト方法

  1. axe DevTools による自動スキャン: ページを Chrome または Firefox で開きます。DevTools(F12)を開き、axe DevTools パネルに移動するかブラウザ拡張機能をインストールし、ページ全体のスキャンを実行します。結果をフィルタリングして、duplicate-idduplicate-id-activeduplicate-id-aria の各ルールを確認します。各違反には、影響を受ける要素とその重複した ID 値が一覧表示されます。監査文書が必要な場合はレポートをエクスポートします。Lighthouse を使用する場合は、DevTools の Lighthouse タブから Lighthouse のアクセシビリティ監査を実行し、「Document has multiple elements with the same id」という監査項目を確認します。
  2. ブラウザ DevTools コンソールによるチェック: ブラウザのコンソールを開き、現在のページ上のすべての重複 ID を見つけるために次の JavaScript スニペットを実行します。const ids = [...document.querySelectorAll('[id]')].map(el => el.id); const dupes = ids.filter((id, i) => ids.indexOf(id) !== i); console.log([...new Set(dupes)]); これにより、2回以上現れるすべての ID 値の配列が出力されます。空の配列であれば、重複は存在しません。
  3. NVDA と Firefox を用いたスクリーンリーダーテスト: NVDA を起動した状態でページを読み込みます。for/idaria-labelledby によってラベルが関連付けられているフィールドを含むフォームに移動します。各フィールドを Tab キーで移動し、NVDA が正しいラベルを読み上げているか注意深く確認します。ラベルなしで読み上げられるフィールドや、別のセクションの誤ったラベルが読み上げられるフィールドがある場合、その原因は重複した ID である可能性があります。この手順を、aria-controlsaria-describedby を使用している ARIA ランドマーク領域、モーダルダイアログ、ウィジェットについても繰り返します。
  4. macOS の VoiceOver と Safari: VoiceOver(Command+F5)を有効にします。VoiceOver ローター(Control+Option+U)を使用してフォームコントロールまたはリンクのリストを開き、各コントロールに一意で正しく読み上げられるラベルが付いていることを確認します。モーダルダイアログに移動し、ダイアログが開いたときにダイアログタイトルが正しく読み上げられることを確認します。
  5. JAWS と Chrome: JAWS を起動した状態でページを開き、JAWS のフォームフィールド一覧(Insert+F5)を使用して、すべてのフォーム要素とそれに関連付けられたラベルを確認します。本来は別々であるべきフィールドが、同じラベルテキストを共有していないことを確認します。
  6. 動的コンテンツのテスト: ページが無限スクロール、シングルページナビゲーション、JavaScript によって挿入されるモーダルダイアログなどを使用している場合は、新しいコンテンツを DOM に読み込むためにこれらの機能を操作し、その後で自動スキャンやコンソールスニペットを再実行して、動的コンテンツによって導入された重複がないか確認します。

修正方法

繰り返しセクション間でフォームフィールドの ID が重複している — 誤り

<!-- Shipping Address -->
<label for='city'>City</label>
<input type='text' id='city' name='shipping-city'>

<!-- Billing Address -->
<label for='city'>City</label>
<input type='text' id='city' name='billing-city'>
<!-- FAIL: Both inputs share id='city'. The second label's 'for' attribute
     resolves to the first input, so screen readers announce the wrong field. -->

繰り返しセクション間でフォームフィールドの ID が重複していない — 正しい

<!-- Shipping Address -->
<label for='shipping-city'>City</label>
<input type='text' id='shipping-city' name='shipping-city'>

<!-- Billing Address -->
<label for='billing-city'>City</label>
<input type='text' id='billing-city' name='billing-city'>
<!-- PASS: Each input has a unique ID scoped to its section.
     Screen readers correctly announce each field's label. -->

再利用コンポーネントが複数回レンダリングされている — 誤り

<!-- Product Card 1 -->
<div class='product-card'>
  <img id='product-img' src='shoe.jpg' alt='Running Shoe'>
  <button id='add-to-cart' aria-describedby='product-desc'>Add to Cart</button>
  <p id='product-desc'>Free shipping on orders over 500 TL.</p>
</div>

<!-- Product Card 2 (same template, duplicate IDs) -->
<div class='product-card'>
  <img id='product-img' src='boot.jpg' alt='Hiking Boot'>
  <button id='add-to-cart' aria-describedby='product-desc'>Add to Cart</button>
  <p id='product-desc'>Free shipping on orders over 500 TL.</p>
</div>
<!-- FAIL: IDs duplicated across cards. aria-describedby on the second button
     resolves to the <p> in the first card, not the second. -->

再利用コンポーネントが複数回レンダリングされている — 正しい

<!-- Product Card 1 -->
<div class='product-card'>
  <img id='product-img-1' src='shoe.jpg' alt='Running Shoe'>
  <button id='add-to-cart-1' aria-describedby='product-desc-1'>Add to Cart</button>
  <p id='product-desc-1'>Free shipping on orders over 500 TL.</p>
</div>

<!-- Product Card 2 -->
<div class='product-card'>
  <img id='product-img-2' src='boot.jpg' alt='Hiking Boot'>
  <button id='add-to-cart-2' aria-describedby='product-desc-2'>Add to Cart</button>
  <p id='product-desc-2'>Free shipping on orders over 500 TL.</p>
</div>
<!-- PASS: Each card's IDs are unique. ARIA references resolve correctly
     within their own card. Use a counter, UUID, or slug-based strategy
     to generate IDs in your component framework. -->

モーダルダイアログで ARIA ラベル参照が重複している — 誤り

<!-- A generic heading used as a reusable ID -->
<h1 id='dialog-title'>Welcome</h1>

<div role='dialog' aria-modal='true' aria-labelledby='dialog-title'>
  <h2 id='dialog-title'>Confirm Your Order</h2>
  <p>Are you sure you want to place this order?</p>
  <button>Confirm</button>
  <button>Cancel</button>
</div>
<!-- FAIL: Two elements share id='dialog-title'. The dialog's
     aria-labelledby resolves to the page <h1>, not the dialog heading.
     Screen readers will announce 'Welcome' as the dialog name. -->

モーダルダイアログで ARIA ラベル参照が重複していない — 正しい

<h1>Welcome</h1>

<div role='dialog' aria-modal='true' aria-labelledby='confirm-dialog-title'>
  <h2 id='confirm-dialog-title'>Confirm Your Order</h2>
  <p>Are you sure you want to place this order?</p>
  <button>Confirm</button>
  <button>Cancel</button>
</div>
<!-- PASS: The dialog heading has a unique, descriptive ID.
     aria-labelledby correctly identifies the dialog to screen readers
     as 'Confirm Your Order'. -->

よくある間違い

  • ID を更新せずにコンポーネントのマークアップをコピーペーストすること: 開発者は、2つ目の住所ブロック、2つ目のタブパネル、2つ目のアコーディオン項目など、2つ目のインスタンス用に動作している HTML セクションを複製し、すべての ID 値を一意に更新し忘れることがよくあります。component-name-index(例: accordion-panel-1accordion-panel-2)のような命名規則を確立し、コードレビューで徹底してください。
  • 一意キー戦略なしにフレームワークコンポーネントで静的 ID を使用すること: React、Vue、Angular などのフレームワークは、同じコンポーネントを1ページに何十回もレンダリングすることがあります。再利用コンポーネント内でハードコードされた id='search-input' を使用すると、インスタンスの数だけ重複が発生します。常に props、カウンタ、React 18+ の useId() のようなユーティリティから ID を導出してください。
  • HTML を修正せずに CSS クラスのターゲティングに頼ること: 一部の開発者は、重複した ID の問題を回避するために、JavaScript のセレクタを getElementById からクラスを使った querySelector に切り替え、重複した ID をそのまま残してしまいます。これは視覚的な挙動を修正するかもしれませんが、アクセシビリティツリーの関連付けの破綻は何も解決しません。
  • サーバーサイドテンプレートのループで毎回同じ ID を生成してしまうこと: {% for item in items %} ループ内で id='item-title' をレンダリングする Jinja2、Blade、Twig テンプレートは、リスト内のアイテムごとに重複を1つずつ生成します。常にループインデックスやアイテム識別子を ID に付加してください。例: id='item-title-{{ loop.index }}'
  • 非表示または画面外の要素にある重複 ID を無視すること: display: nonevisibility: hidden が指定された要素も DOM 上には存在しており、その ID も登録されています。テンプレートとして非表示にしているモーダルが、表示中の要素と ID を共有している場合、同じパースの失敗を引き起こします。hidden 属性を使用するか、非表示テンプレートが一意の ID を使用していることを確認してください。
  • Shadow DOM にスコープされていれば問題が解決すると決めつけること: ネイティブ Shadow DOM 内の ID はスコープされており、ライト DOM や他のシャドウルート内の ID と衝突しません。しかし、多くのコンポーネントライブラリは、真のスコープを提供しないポリフィルや非標準のアプローチを使用しています。フレームワークの挙動を前提とするのではなく、実際の DOM 出力を検証してください。
  • ユーザー提供コンテンツに基づいて、サニタイズや重複排除なしに ID を生成すること: 商品名、記事タイトル、その他の動的テキストから ID を作成すると、同じ名前を持つ2つのアイテムがある場合に衝突が発生します(例: 2つの製品がどちらも「Classic」と呼ばれ、どちらも id='classic' を生成する)。コンテンツ由来の ID には、常に一意のデータベースキーやインデックスを付加してください。
  • シングルページアプリケーションでクライアントサイドナビゲーション後のテストを行わないこと: フルページリロードなしに新しいルートコンテンツを DOM に挿入する SPA は、古いコンテンツが適切にアンマウントされない場合、以前に訪れたルートからの ID を蓄積してしまうことがあります。初回ロード時だけでなく、ルート間を移動した後にも axe スキャンを実行してください。
  • SVG の <defs><use> 要素で使用される ID を見落とすこと: <defs> 内で ID を持つシンボルを定義し、<use href='#icon-arrow'> で参照する SVG スプライトパターンは、同じシンボル定義がページ上に複数回含まれている場合、重複した ID を生み出す可能性があります。SVG スプライト定義を一元化し、1回だけ含めるようにしてください。
  • サードパーティのウィジェット、チャットプラグイン、アナリティクススクリプトによって生成される ID を見落とすこと: サードパーティスクリプトは、ハードコードされた ID を持つ要素を挿入することがあります。自分のコードが同じ ID を使用していると、開発中には気づかない衝突が発生します。サードパーティコンテンツを含む最終的なレンダリング DOM 全体を監査し、衝突があればベンダーに報告するか、自分の ID に名前空間を付けて衝突を回避してください。

トルコのアクセシビリティ規制との関係

トルコの大統領通達 2025/10は、2025年6月21日付の官報(番号 32933)で公布され、トルコで事業を行う幅広い公的・民間組織に対して、ウェブアクセシビリティ要件を義務付けています。この通達は WCAG 2.2 を技術的な参照標準として採用しており、レベル A 準拠をすべての対象組織にとって最低限の法的基準としています。

WCAG 4.1.1 Parsing はレベル A の達成基準です。W3C が WCAG 2.2 でこれを廃止したとはいえ、その主な懸念事項である一意の ID を強制する axe-core のルールは依然として有効であり、WCAG 2.2 に基づいて実施されるアクセシビリティ監査で引き続きフラグされます。自動スキャンツールを用いるトルコの規制監査や適合性レビューでは、仕様レベルでこの達成基準が廃止されているかどうかに関係なく、duplicate-id の違反をレベル A の潜在的な不適合としてフラグすることになります。適合性を示そうとする組織は、重複 ID の違反をブロッキングな問題として扱うべきです。

大統領通達 2025/10 の対象となる組織には、幅広い公的機関と民間セクター組織が含まれます。すべての中央および地方政府機関とその関連機関、トルコの銀行法の下で規制される銀行および金融機関、病院および民間医療提供者、20万以上の加入者にサービスを提供する通信事業者、EC プラットフォームおよびオンラインマーケットプレイス、旅行代理店およびツアーオペレーター、公的コンセッションの下で運営される民間交通事業者、そして国民教育省(MoNE)に認可された私立学校および教育機関が含まれます。

この通達は段階的なコンプライアンスタイムラインを定めています。公的機関は、通達の公布日から1年以内にレベル A の完全な準拠を達成しなければなりません。対象となる民間セクター組織には、同じ基準に到達するために2年の猶予があります。遵守しない場合、対象組織は規制当局による精査、行政制裁の可能性、そしてアクセシビリティへの意識が高まる市場における評判リスクにさらされます。

トルコの組織にとって、重複 ID の違反への対応は、デジタルフォーム、オンライン決済フロー、行政サービスのポータル、医療予約システムなどの文脈で特に重要です。これらはまさに、重複した ID を導入しがちな、繰り返しフォームセクション、再利用コンポーネント、サードパーティウィジェット統合を多用するインターフェイスの種類です。開発プロセスの一環として axe-core を CI/CD パイプラインに統合するなど、自動アクセシビリティテストを確立することは、技術的なベストプラクティスであると同時に、この通達の要件の下で継続的な規制遵守を維持するための現実的な戦略でもあります。