a11y について
ウェブインターフェースを設計・実装する開発者向けのアクセシビリティ実践スキルです。セマンティック HTML、キーボード操作、フォーカス、フォーム、コントラスト、モーション、ページ構造を扱い、短い確認手順も含みます。
主な機能
- 01セマンティック HTML の優先
- 02アクセシブルな名前とキーボード操作
- 03フォーカス、ARIA、ダイアログの指針
- 04フォーム、コントラスト、モーションへの対応
- 05構造、ズーム、時間制限への配慮
- 065 分で行うアクセシビリティ確認
活用シーン
- 01キーボードで操作できる画面を設計する
- 02フォームのラベルとエラー表示を確認する
- 03ダイアログのフォーカス管理を改善する
- 04ズーム、コントラスト、モーション軽減を確認する
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ paragonhq/ stealth-skills --skill a11yインストール後、Claude Code で/a11yを入力して読み込まれたか確認します。
詳細
a11y
アクセシブルなインターフェースは、優れたインターフェースとは別物ではありません。ここにあるルールのほとんどは、マウスを使い、視力に問題のない人にとってもインターフェースをより使いやすくします。コンプライアンスではなく、技術として捉えましょう。
まずはHTMLから
要素そのものが振る舞いを担います。適切な要素を使いましょう。
| 目的 | 使うもの | 使わないもの |
|---|---|---|
| 操作を実行するもの | <button type="button"> | <div onClick> |
| ページ遷移するもの | <a href> | router.pushを呼び出す<button> |
| リスト | <li>を含む<ul>/<ol> | div |
| フォーム | <button type="submit">を含む<form> | フォーム外のボタンに設定したonClick |
| チェックボックス、ラジオボタン、スイッチ | <input type="checkbox">、またはボタンに設定したrole="switch" | カスタムのdiv |
| 見出しのあるセクション | <section aria-labelledby> | 太字の段落を含むdiv |
| 強調 | <strong>、<em> | <b>、<i>、またはfont-weightを設定したspan |
| ダイアログ | <dialog>、またはrole="dialog"を持つプリミティブ | 固定表示のdiv |
ネイティブ要素なら、キーボード操作、フォーカス、ロール、名前が最初から備わっています。これらをカスタム要素で置き換える場合は、4つすべてを自分で補う必要があります。
名前
すべてのコントロールにはアクセシブルな名前が必要です。優先順位は次のとおりです。
- 要素内にある表示テキスト(
<button>変更を保存</button>)。 - 表示されている
<label for>(入力欄)。 - 表示テキストを参照する
aria-labelledby。 - アイコンのみのコントロールに限り
aria-labelを使います。アイコンが何かではなく、コントロールが何をするかを表します(「リンクをコピー」であり、「クリップボードのアイコン」ではありません)。
画像には、画像が伝える内容を示すaltを設定します。装飾目的の場合はalt=""にします。ラベル付きコントロール内のアイコンにはaria-hiddenを設定します。
名前は、その文脈内で一意にします。リストに「編集」ボタンが5つある場合は、表示上またはaria-labelで「第3四半期のレポートを編集」とします。
キーボード
- 操作可能な要素はすべて、視覚的な順序でTabキーから到達できるようにします。
tabindexは0または-1とし、正の値は使いません。 - カスタムウィジェットは、そのロールに対応するARIAパターンに従います。メニュー、タブ、リストボックス、ラジオグループ内では矢印キーを使い、Tabキーでウィジェットの外に移動します。
- Escapeキーで閉じるのは、最前面のレイヤーだけです。
- ダイアログを開いたらフォーカスを中に移動し、開いている間はフォーカスを閉じ込めます。閉じたら、起動元にフォーカスを戻します。
- モーダルの背後にあるコンテンツには
inertを設定します。 - スキップリンクは、最初にフォーカス可能な要素とし、フォーカス時に表示します。
- テキストフィールドにフォーカスがある間は単一キーのショートカットを発動させません。修飾キーを使うショートカットは、ブラウザーのショートカットと競合させません。
- 複合ウィジェットではロービングtabindexまたは
aria-activedescendantを使い、ウィジェットごとにTabキーで移動できる箇所を1つにします。
フォーカスの視認性
:focus-visible { outline: 2px solid var(--fg); outline-offset: 2px; }
:focus:not(:focus-visible) { outline: none; }
- あらゆる背景色で見えるようにします。最も暗い色と最も明るい色の両方でテストします。
- 要素の角丸に合わせます。
- 代わりとなる表示が要素とその背景の両方に対して3:1以上のコントラストを持たない限り、
outline: noneは使いません。 - リンクとして機能する行やカードでは、テキストだけでなく行全体にフォーカスを表示します。
ARIAは必要最小限に
ARIAの第一のルールは、HTMLで実現できる場合はARIAを使わないことです。そのうえで、次のようにします。
- 展開する要素のトリガーには
aria-expandedを設定し、対象要素を指すaria-controlsを設定します。 - 現在のナビゲーションリンクには
aria-current="page"を設定します。ページ内ナビゲーションにはaria-current="location"を設定します。 - トグルボタンには
aria-pressed、スイッチとチェックボックスにはaria-checked、タブとオプションにはaria-selectedを設定します。 - 読み込み中の領域には
aria-busyを設定します。無効なコントロールにフォーカスを維持する必要がある場合はaria-disabledを設定します。 - 入力欄からエラーとヒントを参照するには
aria-describedbyを使います。 - トーストやステータスを通知する領域には
aria-live="polite"を設定します。assertiveを使うのは、処理を妨げるエラーに限ります。 - 装飾アイコン、視覚的な重複(アニメーションのために単語を複数のspanに分割する場合は、親要素に
aria-labelを設定します)、バックドロップにはaria-hidden="true"を設定します。 - ボタンで実現できる場合、divに
role="button"を設定してはいけません。ロールのないdivにaria-labelを設定してはいけません。
フォーム
- すべてのフィールドに見えるラベルを付けます。プレースホルダーはヒントです。
- 必須フィールドには印を付け、その印の意味を一度説明します。
- エラーは、フィールドからフォーカスが外れた後、または送信後に表示し、フィールドの下に配置して
aria-describedbyで関連付けます。送信時には、最初の無効なフィールドにフォーカスを移します。 - エラーテキストでは、何が問題で、どうすれば解決できるかを言葉で伝えます。色とアイコンも添えますが、それらで代用してはいけません。
- ラジオボタンやチェックボックスのグループは、
<legend>を含む<fieldset>に入れます。 - ブラウザーや支援技術が入力を補完できるように
autocompleteを設定します。 - どのフィールドからでも Enter キーで送信できるようにします。
色とコントラスト
- どちらのテーマでも、テキストは 4.5:1、大きなテキストと UI の境界は 3:1 を確保します。
- 色だけを唯一の手がかりにしてはいけません。ステータスにはアイコンか言葉を添え、本文中のリンクには下線を付け、グラフにはパターンかラベルを付けます。
- プレースホルダーと無効状態のテキストは、読み取る必要がない場合に限り、コントラスト比を 4.5:1 未満にできます。
- フォーカスリングと選択色が、すべての背景面に対して適切か確認します。
prefers-contrast: moreの場合は、境界線の色を--fg-2にし、半透明をなくします。forced-colors: active(Windows のハイコントラストモード)の場合は、背景色に依存していたコントロールに境界線を付け、フォーカスにはoutlineを使い、意味を伝えるためのbackground-imageは使いません。
モーション
prefers-reduced-motion: reduceの場合は、トランジションとアニメーションをほぼゼロにし、スムーズスクロールと視差効果を無効にして、自動再生動画を一時停止します。ただし、何も非表示にしてはいけません。アニメーションで表示される要素は、そのまま表示しておきます。- 1 秒あたり 3 回を超えて点滅させてはいけません。
- 5 秒を超える自動再生の動きには、一時停止コントロールを付けます。
構造
- ページごとに
h1を 1 つだけ使い、見出しのレベルを飛ばさず順番に並べます。スクリーンリーダーの利用者が移動したい各領域に見出しを付けます。 - ランドマークには、
main、header、footer、nav(複数ある場合はラベルを付ける)、aside、searchを使います。 - リストにはリストを使います。行ベースの UI には、
aria-labelを指定した<ul>を使います。 - 表には
<th scope>を含む<table>を使うか、適切なロールを持つグリッドを使います。divは使いません。 - ドキュメントと、別の言語で書かれた箇所には
langを指定します。 - DOM の読み上げ順序を視覚的な順序に合わせます。CSS の
orderとflex-direction: row-reverseはこれを崩します。
サイズとズーム
- WCAG に従い、ターゲットの最小サイズを 24px とします。タッチ操作では実際には 44px を確保します。
- ブラウザーのズームを 200% にした場合も、幅 320px の場合も、横スクロールやコンテンツの欠落なくページを使えるようにします。
- テキストの間隔(line-height 1.5、letter-spacing 0.12em、word-spacing 0.16em)を広げても、テキストがクリップされないようにします。
- ピンチズームを無効にしてはいけません。
時間制限
- トーストは少なくとも 5 秒間表示し、アクションがある場合はさらに長く表示します。ホバー中とフォーカス中は一時停止します。
- セッションがタイムアウトする場合は、延長できる時間を残して警告します。
- 利用に時間制限のある応答を求めてはいけません。
5 分間のテスト
- マウスを外します。画面内を Tab キーで移動します。現在の位置が見えますか?すべての操作ができますか?
- VoiceOver(⌘F5)または NVDA を起動します。見出しで移動します(VO+⌘+H)。見出しはページの内容を説明していますか?コントロールまで Tab キーで移動し、それぞれの役割、名前、状態が読み上げられますか?
- ダイアログを開きます。フォーカスはダイアログ内にありますか?Escape キーで閉じて、フォーカスを元の場所に戻せますか?
- ズームを 200% にします。引き続き使えますか?
- モーションの低減を有効にします。何か表示されなくなりますか?
- axe を実行します。「serious」と「critical」の問題をすべて修正します。「moderate」の問題も確認します。多くは実際の問題です。
アクセシブルでない UI の兆候
| 兆候 | 修正方法 |
|---|---|
div onClick | button |
outline: none | :focus-visible のリング |
| 名前のないアイコンボタン | aria-label |
| ラベルとして使うプレースホルダー | 見えるラベル |
| 赤いテキストだけのエラー | アイコン + テキスト + aria-describedby |
| フォーカストラップのないモーダル | ダイアログプリミティブ、背面に inert |
| カスタムセレクト | ネイティブの <select> またはリストボックスプリミティブ |
| ホバー時にしか表示されない操作の手がかり | 常に表示するか、フォーカス時にも表示する |
| 動きを減らすとコンテンツが隠れる | コンテンツは表示し、動きはなくす |
| 色だけで示すステータス | アイコンか言葉 |
カタログから
- Sara Soueidan — アクセシビリティと SVG を、実際に開発している人が解説しています。
- ARIA Authoring Practices — 各パターンのキーボード操作とロールに関する仕様です。
- Inclusive Components — 最初からアクセシブルに構築されたコンポーネントです。
- axe、Polypane — すでに開いているブラウザーでテストできます。
- Nielsen Norman Group — このリストの内容とほぼ全面的に重なるユーザビリティ調査です。
- 詳細は stealth.pm/tools をご覧ください。
コメント
Markdown 対応