ARIAの正しい使い方完全ガイド:WAI-ARIAで“読み上げ・操作・状態”を伝える基本とアンチパターン(WCAG 2.1 AA実務対応)
概要サマリー(先に要点)
- ARIAは便利ですが、使い方を誤るとアクセシビリティを壊すことがある“強い道具”ですの。合言葉は 「まずネイティブ、足りないところだけARIA」。
- 重要なのは 名前(Name)・役割(Role)・状態/値(State/Value) を、支援技術に正しく届けること。
aria-labelだけで解決しようとせず、可視ラベルと一致させ、必要ならaria-describedbyで補足を紐付けます。- モーダル、タブ、メニュー、コンボボックス、ライブリージョンなど、よくある複合UIの“安全な型”を覚えるのが最短です。
- この記事には、現場で事故が起きやすい ARIAアンチパターンと、すぐ差し替えできる実装例を多めに入れました。
対象読者(具体):フロントエンドエンジニア、UI/UXデザイナー、デザインシステム運用担当、QA、CMS開発担当、アクセシビリティ担当、制作会社の実装者
アクセシビリティレベル:WCAG 2.1 AA 準拠を目標(特に 4.1.2 名前・役割・値、2.1.1 キーボード、1.3.1 情報と関係、2.4.6 ラベル など)
1. はじめに:ARIAは“魔法の呪文”ではなく、説明のための言語
WAI-ARIA(ARIA)は、スクリーンリーダーなどの支援技術に対して、UIの意味を補うための仕組みです。たとえば、ボタンが「押せるもの」だと分かるのは、通常は <button> という要素が“役割”を内蔵しているから。
でも、実務では「見た目の自由さ」を優先して div で作られたボタンや、複雑なUI(タブ、メニュー、コンボボックス、モーダル)に出会いますよね。そうした場面で、ARIAはとても頼りになりますの。
ただし、ARIAは“付けたら良くなる”ものではありません。間違ったroleや状態を宣言すると、支援技術に誤情報を送ってしまうのです。
この記事では、ARIAを“正しく・最小で・壊さず”使うための考え方と、よくあるコンポーネントの安全な型を、丁寧に整理していきますね。
2. 絶対に覚えておきたい大原則:「まずネイティブ、足りない分だけARIA」
ARIAの鉄則は、少し強い言い方になりますがこれです。
ARIAは、ネイティブHTMLでできないことだけに使う
2.1 なぜ“ネイティブ優先”なの?
ネイティブ要素(button / a / input / select など)は、最初から
- キーボード操作
- フォーカス
- 役割
- 状態(disabled、checkedなど)
- アクセシブルネームの計算
が揃っていて、ブラウザと支援技術が長年かけて最適化しています。
一方でdiv role="button"は、見た目だけボタンになりがちで、Enter/Space対応、フォーカス、disabledの扱いなどをすべて自前で作る必要が出ます。ミスの余地が一気に増えるんですの。
2.2 “ARIAで直せない”ものがある
ARIAは見た目や操作性そのものを改善しません。
- コントラスト不足
- 小さすぎるタップ領域
- 破綻したTab順
- 文章が難しすぎる
これらはARIAでは救えない領域です。だから、構造と操作を整えた上で、最後にARIAで意味を補うのが正しい順序になります。
3. まずはここ:名前・役割・状態/値(NRV)を整える
ARIAで最も重要なのは、支援技術がUIを理解するための基本情報、いわゆる NRV です。
- Name(名前):それは何と呼ばれる?(例:検索、閉じる、保存)
- Role(役割):それは何?(ボタン、リンク、タブ、チェックボックス…)
- State/Value(状態/値):今どうなってる?(開いてる/閉じてる、選択中、値は何%…)
3.1 アクセシブルネームの基本:まず“可視ラベル”で作る
可能なら、次の順で名前を作ります。
- ボタン内のテキスト(いちばん自然)
<label>とfor(フォーム)aria-labelledby(複数要素から名前を合成したい)aria-label(最後の手段)
例:見出しの文言をボタン名に使う(aria-labelledby)
<h2 id="dlgTitle">通知設定</h2>
<button aria-labelledby="dlgTitle openBtn" id="openBtn">
開く
</button>
「通知設定 開く」のように、文脈込みで読み上げやすくできます。
3.2 補足は aria-describedby で(説明を“名前”に混ぜない)
名前は短く、説明は別に。これが読み上げの疲れを減らしますの。
<label for="email">メールアドレス(必須)</label>
<input id="email" type="email" aria-describedby="emailHint">
<p id="emailHint">例:example@example.com</p>
4. ARIAアンチパターン:よくある“やりがち”と安全な置き換え
ここは特に大事なので、少し厳しめにいきますね。
4.1 div role="button" を乱用する
問題:Tabでフォーカスできない、Spaceで押せない、disabledが嘘になる…など事故が多い。
置き換え:素直に <button> を使う。
悪い例:
<div role="button">保存</div>
良い例:
<button type="button">保存</button>
4.2 何でも aria-label で解決する
問題:可視ラベルと読み上げが一致しないと混乱します(WCAG 2.5.3 Label in Name)。
置き換え:まず可視テキストを用意し、どうしても必要な時だけ aria-label。
4.3 “隠す”のに aria-hidden="true" を乱用する
問題:画面には見えているのに読み上げだけ消える、という危険な状態を作ります。
置き換え:視覚的に非表示なら hidden や display:none、背景を操作不能にしたいならモーダル時に inert など、目的に合った方法で。
4.4 不要なrole付け(例:role="button" を button に付ける)
問題:冗長で、時に挙動が変わることも。
置き換え:ネイティブに任せる。
(“no ARIA is better than bad ARIA” の精神ですの)
5. コンポーネント別:安全なARIAパターン集(実務の型)
ここからが本番です。よく使うUIを、壊れにくい「型」で覚えていきましょう。
5.1 アイコンボタン(検索・閉じる・共有)
ポイント:アイコン画像は装飾として alt=""、ボタンに名前を付けます。
<button type="button" aria-label="閉じる">
<img src="close.svg" alt="">
</button>
- フォーカス可視(
:focus-visible)も必須です。
5.2 開閉(Disclosure):アコーディオン、詳細表示
ポイント:トリガは button、状態は aria-expanded、対象は aria-controls。
<button aria-expanded="false" aria-controls="faq1" id="btn1">
配送日を変更できますか?
</button>
<div id="faq1" hidden>
はい、発送前であれば変更できます。
</div>
- 開いたら
hiddenを外し、aria-expanded="true"に。 - アコーディオンが複数なら、見出し構造(h3など)と組み合わせると迷いが減ります。
5.3 モーダル(Dialog)
必須要件:
- 開いたらフォーカスをモーダル内へ
- 背景へフォーカスが逃げない(トラップ)
- Escで閉じる
- 閉じたらトリガへ戻す
- タイトルを関連付ける(
aria-labelledby)
例(基本形)
<button id="open">設定を開く</button>
<div role="dialog" aria-modal="true" aria-labelledby="dlgTitle" hidden id="dlg">
<h2 id="dlgTitle">通知設定</h2>
<p id="dlgDesc">通知の種類を選んで保存してください。</p>
<label><input type="checkbox"> メール通知</label>
<label><input type="checkbox"> プッシュ通知</label>
<div class="actions">
<button type="button" id="save">保存</button>
<button type="button" id="close">閉じる</button>
</div>
</div>
補足:背景を完全に沈黙させたい場合、対応環境では inert を使うと安全です(背景のフォーカス・操作を無効化)。
モーダルは事故が起きやすいので、デザインシステムで“標準部品”化する価値が非常に高い領域ですわ。
5.4 タブ(Tabs)
基本:
role="tablist"- タブは
role="tab"(通常はbutton) - パネルは
role="tabpanel" - 選択状態は
aria-selected - 矢印でタブ移動(ロービングTabIndex)
<div role="tablist" aria-label="商品情報">
<button role="tab" id="t1" aria-controls="p1" aria-selected="true" tabindex="0">概要</button>
<button role="tab" id="t2" aria-controls="p2" aria-selected="false" tabindex="-1">仕様</button>
<button role="tab" id="t3" aria-controls="p3" aria-selected="false" tabindex="-1">レビュー</button>
</div>
<section role="tabpanel" id="p1" aria-labelledby="t1">…</section>
<section role="tabpanel" id="p2" aria-labelledby="t2" hidden>…</section>
<section role="tabpanel" id="p3" aria-labelledby="t3" hidden>…</section>
注意:タブは“見た目の切替”ではなく、同一文脈内の表示切替に使います。ページ遷移に近いものは、タブよりナビ(リンク)として作ったほうが自然なことが多いですの。
5.5 ライブリージョン(通知:保存完了、検索件数)
ポイント:ユーザー操作の結果を“静かに”伝える。成功は role="status"、エラーは role="alert" が目安。
<div id="status" role="status" aria-atomic="true"></div>
<script>
function saved(){
document.getElementById('status').textContent = '保存が完了しました。';
}
</script>
aria-atomic="true"は文全体を読み上げたいときに便利です。
5.6 コンボボックス(検索候補、オートコンプリート)
ここは難所です。実装の難易度が高く、ARIAの誤用が起きやすいので、可能なら成熟したUIライブラリやネイティブ要素(datalistなど)を検討して、無理に自作しないのが安全ですわ。
どうしても自作するなら、role="combobox"、aria-expanded、aria-controls、aria-activedescendant などを正しく扱い、矢印操作と読み上げを成立させます。
(この領域は1本記事が書けるほど奥深いので、デザインシステム側で“採用する方式”を固定するのが現実的です。)
6. 文章・ラベル・状態の設計:ARIA以前に“言葉”で迷わせない
ARIAを付けても、ラベルが曖昧だと使えません。たとえば「こちら」「詳細」「送信」だけだと、リンク一覧やボタン一覧で意味が崩れますの。
6.1 ラベルは“動詞+目的語”が強い
- 悪い:詳細
- 良い:注文の詳細を見る、配送先を変更する、領収書をダウンロードする
6.2 状態は視覚と音声で二重化
- 展開状態:
aria-expanded+「開く/閉じる」表示 - 選択状態:
aria-selected+視覚的強調 - エラー:
aria-invalid+エラーテキスト
7. テストのしかた:ARIAは“付けたか”ではなく“動いたか”で判断
ARIAは宣言なので、最終的に正しいかどうかは操作と読み上げで確認するのがいちばん確実です。
7.1 5分スモーク(ARIA観点)
- Tabで主要操作を完遂できる
- ボタン/リンクの名前が具体的(一覧で見ても分かる)
- 開閉UIで
aria-expandedが状態に追随している - モーダルで背景に逃げない、Escで閉じて戻る
- エラーで
aria-invalidとaria-describedbyが適切 - 状態通知(保存完了など)が必要な箇所で
status/alertが機能する
7.2 画面読み上げで見るポイント(最低限)
- 「ボタン」「チェックボックス」など役割が正しい
- 「選択済み」「展開済み」など状態が読める
- ラベルが「アイコン名」ではなく、目的になっている
8. デザインシステムに落とす:ARIAを“チームの標準”にするコツ
前回の記事(デザインシステム)とつなげるなら、ARIAは次の形で組み込むのが強いですわ。
8.1 コンポーネント仕様に「ARIAの最小セット」を明記
例:Disclosureなら
- 必須:
aria-expanded/aria-controls - 状態:開閉に追随
- 禁止:
div role="button"
こうしておくと、実装のばらつきが減ります。
8.2 “実装例”を1つに固定する
複数の書き方を許すと、結局レビューが地獄になります。
「タブはこのパターン」「モーダルはこのパターン」と決め、コードを共有するのが最も効きます。
8.3 受け入れ基準は“操作結果”で
- 「tablistがある」ではなく
- 「矢印でタブを移動でき、選択状態が読み上げで分かる」
のように、結果で決めると壊れません。
9. 対象読者にとっての価値(具体)
- スクリーンリーダー利用者:ボタンや状態が正しく読めると、操作の迷子が激減し、作業が途切れません。
- キーボード利用者:ARIAだけでなく、ネイティブ要素優先の設計が進むことで、操作が自然に成立します。
- 認知特性のある方:状態が明確(開いてる/閉じてる、選択中など)だと、画面変化に置いていかれにくくなります。
- 開発チーム:アンチパターンを避け、標準の型を持つことで、回帰バグとレビューコストが減ります。
- デザインシステム運用:ARIA要件が仕様化されると、複数プロダクトでも同じ品質を再現できます。
10. アクセシビリティレベルの評価(本記事の到達点)
- WCAG 2.1 AA を支える中核
- 4.1.2 名前・役割・値:ARIAの正しい付与、状態の露出
- 2.1.1 キーボード:ネイティブ要素優先、モーダル/タブの操作規則
- 1.3.1 情報と関係:適切な構造化(タブ/フォーム関連付け)
- 2.4.6 見出し及びラベル:ラベルの具体化、名前の一致
- 2.4.7 フォーカス可視:ARIAではなくCSSとデザインシステムで担保
- 3.3.x 入力支援:エラーの関連付け(
aria-invalid/aria-describedby)
- つまり本記事は、AA準拠の中でも特に“壊れやすいUI領域”を、ARIAの正しい使い方で安定させることを狙っています。
11. まとめ:ARIAは“最小で正確に”がいちばん強い
- まずネイティブHTML。ARIAは足りないところだけに使う。
- NRV(名前・役割・状態/値)を整えると、読み上げ体験が安定する。
aria-label依存ではなく、可視ラベル+aria-describedbyで補足する。- モーダル、タブ、開閉UI、通知など、複合UIは“安全な型”で統一する。
- アンチパターン(divボタン、aria-hidden乱用、不要role)を避ける。
- デザインシステムに仕様と受け入れ基準を入れ、継続的に守る。
ARIAは、やさしさを“正しい言葉”で伝えるための道具です。
最小で、正確に、そして継続できる形で。あなたのUIが、誰にとっても迷いにくい場所になりますように。わたしも心を込めてお手伝いしますね。
