references/component-tracing.md
A supporting file of the a11y-check-code skill.
コンポーネントの追跡
チェック対象のファイルから import を辿り、実際にレンダリングされるマークアップの全体像を 把握するための手順。
基本方針
アクセシビリティの問題は、マークアップが最終的にどう組み上がるかによって決まる。 1つのファイルだけを見ても、以下は判定できない。
- そのコンポーネントが受け取る
childrenに何が入るか - ラップしているコンポーネントが
<button>を出すのか<div>を出すのか idが正しく<label for>と紐付いているか(生成が別ファイルにある場合)- そのボタンが
aria-expandedを持つかどうか(共通コンポーネント側の実装次第)
そのため、対象ファイルから import されているコンポーネントを辿る必要がある。
追跡の範囲
辿るもの
- 自プロジェクト内のコンポーネント(相対パス、および
@/~/src/などの エイリアスによる import) - 対象コンポーネントを構成するスタイル定義(CSS Modules、
.css、.scss、 Tailwind の設定、デザイントークン) - 対象が使われている側のファイル(呼び出し元)。props に何が渡されるかで結論が変わる観点 (SEM-01 の代替テキスト、SEM-08 のラベル、SEM-11 のロール)では、呼び出し元の確認が必要になる
限定的に辿るもの — 外部パッケージの API コントラクト
UI コンポーネントを npm パッケージで提供する例は多く、node_modules をまったく見ないと
判断材料がほとんど得られないことがある。そこで、package.json の dependencies に
直接指定されているパッケージに限り、その API コントラクトの確認のために辿ってよい。
ただし用途は次に厳しく限定する。
目的は「props の契約」を確かめること。 実装を読んで最終的な DOM を推論することではない。 主に読むのは、そのパッケージの以下である。
- 型定義(
.d.ts)— どんな props を受け取るか、alt/labelに相当する props が 必須か任意か、aria-*を forward するか - README / JSDoc の注記 — props の意図、アクセシビリティに関する使い方の前提
これで裏を取れるのは「props 次第で結論が変わる観点」(SEM-01 / SEM-03 / SEM-08 など、
後述の表)の呼び出し側の使い方である。「このコンポーネントは label を必須にしている/
していない」といった設計上の事実は、ここから確実に読める。
辿らないもの・根拠にしないもの:
- パッケージの内部実装(レンダリング結果の DOM)を推論の根拠にしない。
node_modulesの多くはビルド済み・minify 済みで、型や注記が示すのは API の形であって 出力されるマークアップではない。JSDoc の「accessible」表記は意図であって保証ではない (それを根拠に「問題なし」と書くと見逃しに倒れる) - 推移的依存(
dependenciesの依存の依存)は辿らない。 a11y の実体を内部パッケージに 委譲するライブラリは多いが(例:@mui/material→@mui/base、Radix の各コンポーネント→@radix-ui/react-*)、そこまで追うと深さ・ファイル数の上限を即座に超え、レビュー本体の 精度を削る。直接依存の API 契約の確認で止める - ソースを同梱していない(読めるのがビルド成果物だけの)パッケージは、無理に読まず 「未確認」に回す
- テストコード、Storybook のストーリー(ただし、状態バリエーションの列挙には役立つので、 存在する場合は状態の把握に読んでよい)
そして、API 契約を確認したかどうかにかかわらず、ライブラリ起因の観点は引き続き
「実ページでの確認が必要」として扱う。 accessible なライブラリほど useId・状態フック・
portal・実行時計算の ARIA を多用し、これは静的追跡では確定できない領域(後述「動的な
マークアップ」)そのものだからである。パッケージ名とバージョンは、これまで通り必ず記録する
(package.json を確認する)。バージョンによって実装は変わる。
上限
既定の上限は 深さ3・ファイル数50 とする。これを超える場合は、そこで辿るのをやめ、 利用者に範囲を確認する。
対象から辿ったコンポーネントが50ファイルを超えました。
以下の範囲に絞ってチェックを進めてよいですか、それとも範囲を分割しますか。範囲が広すぎる状態でチェックを進めると、1つ1つの確認が浅くなり、見落としが増える。 広く浅く見るよりも、範囲を区切って深く見るほうがよい。
追跡の記録
辿った結果を、以下のような形で利用者に提示してから次の手順に進む。
チェック対象のコンポーネントツリー
src/pages/Checkout.tsx
├── src/components/Form/AddressForm.tsx
│ ├── src/components/ui/TextField.tsx
│ └── src/components/ui/Select.tsx
├── src/components/Cart/CartSummary.tsx
│ └── src/components/ui/Table.tsx
└── src/components/ui/Button.tsx
外部ライブラリ(API 契約のみ確認・内部実装と実挙動は未確認)
- @headlessui/react 2.1.2 … Dialog, Listbox
- react-hook-form 7.51.0
スタイル
- tailwind.config.ts
- src/styles/tokens.css外部ライブラリを別枠で示すのは、そこに起因する問題を「内部実装と実挙動は確認していない」と
明示するため。型定義や JSDoc で props の契約まで確認した場合も、その結論(例:「Dialog は
aria-modal を持つ」)は API の形として読めたことを意味するにすぎず、レンダリング結果や
実行時の挙動を検証したことにはならない。ここは常に実ページ確認へ回す。
判定に呼び出し元の確認が必要な観点
以下の観点は、コンポーネント単体では判定できない。呼び出し元を Grep で探す。
| 観点 | 呼び出し元で確認すること |
|---|---|
| SEM-01 | alt に相当する props に、実際に何が渡されているか |
| SEM-03 | ラベルの props が渡されているか。渡されていない使い方がないか |
| SEM-07 | 見出しレベルの props(as="h2" など)が、ページ全体で整合しているか |
| SEM-08 | 表示ラベルと aria-label に別の値が渡されていないか |
| SEM-11 | 汎用コンポーネントに、意味に合わない role が渡されていないか |
| VIS-18 | リンクコンポーネントに「こちら」のようなテキストが渡されていないか |
汎用的な UI コンポーネント(Button、TextField など)をチェックする場合、
その使われ方をすべて確認するのは現実的でないことが多い。その場合は、コンポーネント自体の
実装の問題と、「この props を渡し忘れるとアクセシビリティの問題が起きる」という設計上の
リスクを指摘し、使用箇所の網羅は範囲外として明記する。
動的なマークアップ
以下は静的な追跡では最終的なマークアップを確定できない。「判定不能」として扱い、 実ページでの確認に回す。
dangerouslySetInnerHTML/v-html/{@html}で挿入される HTML- CMS やマークダウンから生成されるコンテンツ
- サードパーティのウィジェット(チャット、広告、埋め込みプレイヤー、同意管理バナー)
- サーバーから受け取ったデータによってロールや状態が決まる実装
これらはアクセシビリティの問題が集中しやすい箇所でもあるため、「判定不能」で終わらせず、 実ページでの確認が必要な箇所としてレポートに明記する。