はじめに 🌟
アクセシビリティは、リリース直前にチェックリストで埋める「特別対応」ではありません。私はむしろ、UI 設計の最初に置くべき制約条件だと考えています。キーボードだけで操作できるか、スクリーンリーダーで意味が伝わるか、フォーカスが迷子にならないか。この前提が揃ってはじめて、見た目の美しさや操作感が活きてきます。
Fluent UI 2 は、この考え方をかなり明確に打ち出しているデザインシステムです。公式の Accessibility ページでも、コンポーネントが WCAG 2.1 AA を満たす、または上回る基盤を提供すると説明されています。一方で、それは「Fluent UI を使えば何もしなくてよい」という意味ではありません。
本記事は、Fluent UI v9 / Fluent 2 を使っている、または採用を検討しているフロントエンド開発者向けに書いています。WCAG 2.1 の主要達成基準と Fluent UI の関係を整理しながら、キーボード操作、支援技術対応、コンポーネントごとの追加実装ポイントまでを実践寄りにまとめます。
特に、Fluent UI が担保してくれる範囲と、プロダクト側が責任を持って設計すべき範囲を切り分けて理解したい方に役立つはずです。まずはこの記事のゴールから確認していきます 🌟
今回のゴール
- ✅ WCAG 2.1 の主要 SC と Fluent UI の対応関係を理解する
- ✅ キーボードナビゲーションの実装パターンを把握する
- ✅ スクリーンリーダー対応の考え方を理解する
- ✅ 代表的なコンポーネントで何を追加実装すべきかを知る
- ✅ 手動テストのチェックリストを得る
ここからは、まず Fluent UI 2 がなぜアクセシビリティを強く打ち出すようになったのかを背景から見ていきます。
背景:なぜ Fluent UI 2 でアクセシビリティが改善されたか
Fluent UI v8 以前にもアクセシビリティへの配慮はありましたが、v9 では React コンポーネント群の再設計とあわせて、ARIA パターン、フォーカス管理、キーボード操作の整備がより前面に出るようになりました。特に converged components の設計では、見た目の統一だけでなく、操作モデルの一貫性も重視されています。
Fluent 2 の公式サイトは次のように説明しています。
“Fluent’s components meet or surpass WCAG 2.1 AA standards to ensure an accessible foundation.”
さらに同じページでは、キーボードナビゲーションと支援技術を前提にした設計、そして “manage focus” の重要性も明示されています。Fluent UI React v9 の実装では @fluentui/react-tabster を通じて Tabster ベースのフォーカス制御が使われており、Dialog や TabList などのコンポーネントでその恩恵を受けられます。
ただし、ここで強調したいのは 「アクセシブルなコンポーネントを使うこと」と「アクセシブルなプロダクトを作ること」は同義ではない という点です。コンポーネントが適切な role やキーボード操作を内包していても、ラベルの欠落、誤った DOM 順、不適切なライブリージョン設計があれば、利用体験は簡単に壊れます。次の章では、その判断軸になる WCAG 2.1 の主要達成基準を見ていきます ♿
WCAG 2.1 の主要達成基準と Fluent UI の対応 ♿
2.1.1 Keyboard(キーボード操作)
“All functionality of the content is operable through a keyboard interface...”
要点は、すべての機能がキーボードから操作できることです。Fluent UI の Button、Dialog、Menu、Tabs、Combobox などは、Tab、Enter、Space、矢印キーといった標準的な操作パターンを前提に設計されています。
一方で、プロダクト側が独自に div をクリック可能にしたり、アイコンだけの独自トリガーを足したりすると、この前提はすぐ崩れます。Fluent UI のコンポーネントを使っていても、カスタム実装部分にキーボード等価操作があるかは必ず確認したいです。
2.1.2 No Keyboard Trap(キーボードトラップなし)
“If keyboard focus can be moved to a component... then focus can be moved away from that component using only a keyboard interface...”
要点は、フォーカスが入ったのに抜け出せない UI を作らないことです。Dialog のようなモーダルでは、意図的にフォーカスを内部に留めることがありますが、それは「閉じる方法が明確である」ことが前提です。
Fluent UI の Dialog は modal / non-modal / alert の振る舞いを分けており、通常の modal dialog は Escape によるクローズやトリガーへのフォーカス復帰を前提にしています。つまり「閉じ込める」のではなく、制御された滞在と安全な退出を設計しているわけです。
2.4.3 Focus Order(フォーカス順序)
“Focusable components receive focus in an order that preserves meaning and operability.”
要点は、フォーカス順が意味と操作性を壊さないことです。Fluent UI は Tabster や roving tabindex 的なパターンで複合ウィジェットの移動を支えますが、ページ全体の DOM 順やレイアウト順までは自動では直してくれません。
たとえば CSS の並べ替えやポータル配置の使い方によって、見た目の順序と Tab 順がずれることがあります。Fluent UI を採用していても、最終的なフォーカス順序はアプリケーションの責任だと理解しておくと判断を誤りにくいです。
2.4.7 Focus Visible(フォーカスの視覚化)
“Any keyboard operable user interface has a mode of operation where the keyboard focus indicator is visible.”
要点は、今どこにフォーカスがあるかを見失わせないことです。Fluent UI のコンポーネントは既定でフォーカス表示を持っていますが、テーマや CSS リセットを上書きすると簡単に消えてしまいます。
特にデザイン調整で outline: none; を安易に入れると、キーボード利用者の操作性を大きく損ねます。テーマカスタマイズ時は、色だけでなく 十分に見えるフォーカスリングが維持されているかを確認したいです。
4.1.2 Name, Role, Value(名前・役割・値)
“For all user interface components... the name and role can be programmatically determined...”
要点は、支援技術が UI の名前・役割・状態を取得できることです。Fluent UI コンポーネントは role="tab"、role="dialog"、aria-selected、aria-expanded のような土台を持っていますが、アクセシブルネームそのものは開発者が与える場面が多くあります。
代表例は、アイコンのみのボタンやタブ、タイトルのない Dialog、ラベルのない Combobox です。Fluent UI が役割や状態を支えてくれるからこそ、最後のラベル付けを人間が丁寧に仕上げることが重要になります。次は実装で迷いやすいキーボードナビゲーションを具体的に見ていきます ⌨️
キーボードナビゲーションの実践 ⌨️
Fluent UI の複合コンポーネントは、WAI-ARIA Authoring Practices Guide に沿う操作モデルをかなり強く意識しています。TabList では矢印キー移動、Menu ではメニュー内移動、Dialog ではフォーカス移動と復帰、といった定番パターンが揃っています。
また、React v9 の実装では @fluentui/react-tabster によるフォーカスグループ管理が使われています。TabList のソースでも useArrowNavigationGroup が使われており、Dialog の spec でも Tabster によるフォーカストラップが明示されています。ここを理解しておくと、「なぜこのキーで動くのか」が読み解きやすくなります。
コンポーネント別キーボード操作
Dialog
- 開くと最初のフォーカス可能要素へフォーカスが移動します
- 通常の modal dialog は Escape で閉じ、トリガーへフォーカスを戻す前提です
-
DialogSurface実装ではrole="dialog"またはrole="alertdialog"、aria-modal、aria-labelledbyが設定されます -
aria-labelを明示した場合はaria-labelledbyを省略する実装になっているため、タイトルコンポーネントがない場合はaria-labelを必ず与えるのが安全です
<Dialog>
<DialogTrigger disableButtonEnhancement>
<Button>設定を開く</Button>
</DialogTrigger>
<DialogSurface aria-label="通知設定ダイアログ">
<DialogBody>
<DialogTitle>通知設定</DialogTitle>
<DialogContent>メール通知の条件を設定します。</DialogContent>
</DialogBody>
</DialogSurface>
</Dialog>
aria-modal は重要な属性ですが、それ自体でモーダル挙動(背景コンテンツのフォーカス・読み上げからの除外)を自動で実装するわけではありません。MDN は「aria-modal だけではモーダルにならず、開発者が実装し十分なテストを行うことが不可欠」と明記しています。WAI-ARIA APG も aria-modal="true" を付けるのは実際にモーダルとして振る舞う場合に限ると注意しており、一部の支援技術との組み合わせによっては意図した挙動と異なる場合もあるため、主要な環境で確認しておくことを推奨します。
出典: Dialog Spec、useDialogSurface.ts
MenuList / MenuItem
-
role="menu"とrole="menuitem"を土台にした構造です - 矢印キーで項目間を移動し、Enter / Space で実行するパターンが基本です
-
aria-labelledbyによりメニュー全体へ名前を与える構造が spec に含まれています - MenuList の実装には先頭文字によるフォーカス移動(first-character navigation)が含まれており、入力キーと各メニュー項目の先頭文字を照合してフォーカスを動かします。矢印キーを中心としたメニュー操作モデルを前提に設計されています
TabList / Tab
-
role="tablist"とrole="tab"の ARIA 構造です -
aria-selectedで現在選択中のタブを表現します -
useArrowNavigationGroupにより、左右または上下の矢印キー移動を支えています -
selectTabOnFocusを使うと自動選択、既定では Enter / Space による手動選択寄りの運用ができます - アイコンのみのタブは可視ラベルを持たないため、
aria-labelの付与が実質必須です
<TabList defaultSelectedValue="files">
<Tab value="files">ファイル</Tab>
<Tab value="history">履歴</Tab>
<Tab value="settings" icon={<SettingsRegular />} aria-label="設定" />
</TabList>
出典: Tabs Spec, useTab.ts, useTabList.ts
Combobox / Listbox / Option
- Combobox は
aria-activedescendantパターンを使い、フォーカス自体は入力欄に置いたままアクティブ項目を示します - リストが開いているときは trigger 要素に
aria-controlsでリストボックスとの関係を表現し、ポータル(非 inlinePopup)配置で開いているときは root にaria-ownsを設定します - Option 側では
aria-selected、マルチセレクト時はaria-checkedが設定されます - ラベルは自動では補えないため、
aria-labelまたはaria-labelledbyを必ず付けます
<label id="pet-label">好きな動物</label>
<Combobox aria-labelledby="pet-label" placeholder="選択してください">
<Option>Cat</Option>
<Option>Dog</Option>
<Option>Ferret</Option>
</Combobox>
出典: Combobox Spec, useCombobox.tsx, useOption.tsx
Tooltip
- Tooltip は補足説明のためのコンポーネントです
- React v9 の仕様では
relationship="description"でaria-describedby、relationship="label"でaria-label/aria-labelledbyを設定できます - ただし通常の UI 説明では、Tooltip を「名前」ではなく「説明」として使う方が誤読を減らしやすいです
- Escape で即座に閉じる挙動が定義されています
- より長い補足やインタラクティブな情報には、
@fluentui/react-infolabel系のコンポーネントを検討する余地があります
出典: Tooltip Spec, Tooltip usage, react-infolabel Spec
ここまでで、Fluent UI がキーボード操作の土台をかなり持っていることが見えてきました。次は、その情報がスクリーンリーダーにどう伝わるかを整理します 🔊
支援技術(スクリーンリーダー)との連携 🔊
スクリーンリーダーは、DOM そのものを読むのではなく、ブラウザーが公開するアクセシビリティツリーを通して UI を解釈します。そこで重要になるのが、role、aria-* 属性、状態変化の通知です。WCAG 4.1.2 の「Name, Role, Value」は、まさにこの土台を整えるための要求です。
Fluent UI が提供する土台
Fluent UI は Dialog、Tab、Menu、Combobox、Tooltip などで、widget roles や composite widget の定番パターンを採用しています。つまり、支援技術が理解しやすい構造に寄せる努力はライブラリ側でかなり進んでいます。
開発者が設計するべき点
ただし、次の部分は依然として開発者の責任です。
- カスタムコンポーネントへの適切なラベル付け
- 動的コンテンツ更新時の通知設計
- Dialog の
aria-label/aria-labelledbyの明示 - アイコンのみの UI 要素への
aria-label
aria-live の実装例
特に通知設計では、aria-live の使い方が重要です。保存完了、検索結果件数変更、入力エラー発生のような 画面が変わらずに状態だけ変わる場面では、視覚だけに依存しないアナウンスが必要です。
<div aria-live="polite" role="status">
{resultCount > 0 ? `${resultCount} 件の候補があります` : '候補はありません'}
</div>
role="alert" や aria-live="assertive" は強い通知なので、多用すると逆に体験を壊します。Fluent UI の MessageBar や Alert 的な UI を使うときも、何を即時に伝えるべきかを設計してから使うのが大切です。次は、主要コンポーネントを一覧で見比べられるように表で整理します 📊
コンポーネント別 WCAG 2.1 対応表 📊
本文では Dialog・TabList・Combobox・Tooltip を中心に詳解しましたが、実装で見落としやすい代表コンポーネントをここで一覧化します。Fluent UI が提供するものと、開発者が追加で確認すべき点を横断的に比較する地図として使ってください。
| コンポーネント | 主な ARIA 実装 | 関連 WCAG SC | 開発者が手動で確認すること |
|---|---|---|---|
| 🗂️ Dialog |
role="dialog", aria-modal, aria-labelledby
|
2.1.1, 2.1.2, 4.1.2 |
aria-labelledby / aria-label の設定、Escape でのクローズ、ネストしたポップアップの AT 挙動 |
| 📋 MenuList / MenuItem |
role="menu" と role="menuitem", aria-labelledby
|
2.1.1, 4.1.2 | メニュー自体の aria-labelledby、トリガーボタンの aria-expanded
|
| 🗂️ TabList / Tab |
role="tablist" と role="tab", aria-selected
|
2.1.1, 2.4.3, 4.1.2 | アイコンのみのタブへの aria-label、タブパネルと tabpanel の関連付け |
| 📝 Combobox / Listbox |
role="combobox"、role="listbox"、role="option", aria-activedescendant
|
2.1.1, 4.1.2 |
aria-label / aria-labelledby の設定、選択状態の通知、フィルタ後の件数通知 |
| 💬 Tooltip |
role="tooltip", aria-describedby
|
4.1.2 | Tooltip をラベルとして誤用しない、キーボードでの表示・非表示、Escape による閉じる |
| 🔘 Button |
role="button", aria-pressed(トグル時) |
2.1.1, 4.1.2 | アイコンのみのボタンへの aria-label、aria-disabled の適切な使用 |
| ✅ Checkbox |
role="checkbox", aria-checked
|
2.1.1, 4.1.2 | グループには fieldset + legend、中間状態(indeterminate)の通知 |
| 📢 Alert / MessageBar |
role="alert" / aria-live
|
4.1.3(参考) | ライブリージョンの適切な使用、過剰な通知を避ける |
この表は、コンポーネントを俯瞰で比較する地図(見落とし防止)として使ってください。次のチェックリストは、実際の画面で確認する作業項目として日常的に使えます ✅
実装チェックリスト ✅
Fluent UI を使ったプロダクト開発でアクセシビリティを確保するためのチェックリストです。
フォーカス管理
- Tab キーで全インタラクティブ要素にフォーカスが当たる
- フォーカス順序が視覚的な読み順と一致している(SC 2.4.3)
- モーダル/ダイアログ外にフォーカスが抜けない(SC 2.1.2)
- モーダルを閉じたらトリガー要素にフォーカスが戻る
- フォーカスリングがテーマカスタマイズ後も視認できる(SC 2.4.7)
ラベル・役割・値
- すべてのインタラクティブ要素にアクセシブルな名前がある(SC 4.1.2)
-
アイコンのみのボタン・タブに
aria-labelを付与した -
Dialog に
aria-labelledbyまたはaria-labelを設定した -
Combobox / Listbox のフィールドに
aria-label/aria-labelledbyを設定した -
フォームフィールドに
<label>要素またはaria-labelを付けた
キーボード操作
- カスタムコンポーネントが Enter / Space で実行できる(SC 2.1.1)
- Escape でモーダル・メニュー・ツールチップが閉じる
-
矢印キーが必要な複合ウィジェット(
menu、listbox、tablistなど)で動作する
スクリーンリーダー
- NVDA + Chrome、JAWS + Chrome、VoiceOver + Safari で主要フローを確認した
-
動的コンテンツ更新が
aria-liveまたはrole="status"またはrole="alert"で通知される -
Tooltip を
aria-labelの代わりに使っていない(aria-describedbyとして使う)
自動テストと手動テスト
- axe-core / Accessibility Insights などで自動スキャンを実施した
- 自動テストで検出できない項目(フォーカス順序、ラベルの適切性など)を手動確認した
自動テストツールはアクセシビリティ問題の一部を機械的に検出できますが、代替テキストの妥当性・フォーカス順序の適切さ・操作の理解しやすさといった判断を要する問題は人の目による確認が不可欠です。W3C も自動評価ツールだけでは完全なアクセシビリティ評価はできないと説明しています(Selecting Web Accessibility Evaluation Tools)。
残りは必ず手動テストとユーザーテストで確認してください。
チェックリストは一度作って終わりではなく、リグレッションを防ぐために継続的に回すことが重要です。最後に、本記事のポイントを短くまとめます 🎯
まとめ 🎯
本記事では、Fluent UI 2 が担う土台と、開発者が追加で設計・検証すべき点を切り分けながら、WCAG 2.1 の主要達成基準・キーボード操作・支援技術連携・コンポーネント別の確認事項を見てきました。
Fluent UI 2 は、WCAG 2.1 AA を見据えたアクセシブルな基盤を提供してくれます。Dialog、TabList、Combobox、Tooltip などの主要コンポーネントは、キーボード操作や ARIA 構造の土台をかなり整えてくれます。
ただし、そこで終わりではありません。実際のアクセシビリティを左右するのは、プロダクトチームによるラベル設計、フォーカス管理、インタラクションフロー、通知設計です。「コンポーネントを使えば自動的にアクセシブルになる」という誤解は捨てた方がよいと思います。
WCAG 2.1 AA を着実に満たすには、コンポーネントを正しく使い、必要な aria-label や aria-labelledby を与え、キーボードと支援技術で主要フローを実機確認することが不可欠です。
そして最後は、自動テストと手動テスト、さらに当事者参加のユーザーテストを組み合わせることです。Fluent UI 2 を「見た目のライブラリ」ではなく、「利用体験を支える設計基盤」として使いこなしていきたいですね ♿