【基礎編】W3Cが書いたアクセシビリティガイド を読んで アクセシビリティ準拠アコーディオン を React で実装しよう!
概要
この記事では、W3Cが提供する ARIA Authoring Practices Guide(以下APG)を読み解き、React でアクセシビリティに考慮したアコーディオンを実装してみます。
基礎編では、APGを読み解き、アコーディオンに関するアクセシビリティ要件を理解することが目的です。
この記事(基礎編/実装編)を読むことで得られること
✅ APG とは何か
✅ APG を理解する流れ
✅ Accordion のアクセシビリティに関する仕様
✅ APG に基づき、自力でアクセシビリティを担保したUIを実装する方法
🔶 アクセシビリティの基本
🔶 WAI-ARIA について
❌ 実稼働環境で即利用可能レベルのコード(APGは実稼働環境向けではないと明言している[1])
この記事(基礎編/実装編)の流れ
- ARIA Authoring Practices Guide (APG) とは?
- APG を理解する
- Accordion Pattern/Example を読み解いてみる
--- 今回はここまで --- - React環境構築
- Accordion Group を実装する
- Accordion Header を実装する
- Accordion Panel を実装する
- 動かしてみる
1. ARIA Authoring Practices Guide (APG) とは?
ARIA Authoring Practices Guide(以下APG)は、W3Cが提供するアクセシビリティに関するUIを実装するための方法を学べるサイトです。
今回はアコーディオンのガイドを読みますが、ボタンやダイアログ、ぱんクズリストなど、様々なUIパターンについてのガイドが提供されています。
IntroductionによるとAPGは以下のように説明されています。(直訳)
APGとは
APG は、複数のアクセシビリティと Web テクノロジ仕様の要件を統合して、支援テクノロジとキーボード インターフェースを利用するユーザー向けの Web エクスペリエンスを開発するためのガイダンスを作成します。APG には、アクセス可能なユーザー インターフェース パターンのライブラリ、パターンの実装例、および次の方法を説明するガイダンス セクションが含まれています。
- Accessible Rich Internet Application (ARIA)仕様の機能を HTML、CSS、JavaScript、その他の Web テクノロジと組み合わせて 使用することで、一般的なユーザー インターフェイス パターンをアクセシブルにします 。
- ARIA、HTML、CSS を使用して、アクセス可能な名前の提供、キーボード操作の有効化、支援技術へのページ構造の伝達、高コントラストのオペレーティング システム設定のサポートなど、基本的なアクセシビリティ プラクティスを実装します。
APG の目的
APG の主な目的は、次のような Web アクセシビリティの実践に関する幅広い理解を構築し、その採用を促進することで、堅牢でアクセスしやすいエクスペリエンスの提供を増やすことです。
- ARIA、HTML、CSS、および関連仕様の意図と一致しています。
- ARIA および支援技術プロジェクトによって評価される支援技術の相互運用性をサポートします。
- 障害のある人の使いやすさをサポートし、WCAG要件に準拠する最新の GUI を開発するための実用的な手法を推進します。
2. APG を理解する
APGは各パターンごとに、Pattern(パターンページ) と Example(実装例ページ) の2ページに全ての仕様が定義されています。
各ページには以下のセクションで記述されています。
Pattern(パターンページ)
About This Pattern
このパターンについての概要が記載されています。
主にどのような機能を持っているか・どのような目的で使われるかなどです。
Example
Example(実装例ページ)へのリンクが記載されています。
Keyboard Interaction
このパターンで実装されたコンポーネントがサポートしているキーボード操作について記載されています。
キーと、そのキーに対する操作内容が記載されています。
WAI-ARIA Roles, States, and Properties:
このパターンを実装する際に使用される WAI-ARIAの名称と、その役割が記載されています。
Example(実装例ページ)
About This Example
Exampleで実装されたコンポーネントについての機能概要が記載されています。
Example
Exampleのコードの実行結果です。
実際に操作することが可能です。
Accessibility Features
このExampleで実装されているアクセシビリティに関する機能が記載されています。
Keyboard Support
キーと、そのキーに対する操作内容が記載されています。
Pattern.Keyboard Interaction と一緒に見ると理解しやすいです。
Role, Property, State, and Tabindex Attributes
このExampleで使用されている WAI-ARIAの名称と、その役割が表形式で記載されています。
Pattern.WAI-ARIA Roles, States, and Properties: と一緒に見ると理解しやすいです。
Assistive Technology Support
全てのページに記載れているわけではないので、割愛します。
ARIA-ATプロジェクトによって実行された相互運用性テストの実用的な概要が記載されています。
このセクションの読み方は、以下にわかりやすく記載されています。
JavaScript and CSS Source Code
Exampleのコードの実行結果のJSとCSSソースコードが記載されています。
CodePen で実行することが可能です。
HTML Source Code
Exampleのコードの実行結果のHTMLソースコードが記載されています。
3. Accordion Pattern/Example を読み解いてみる
長くなりましたが、ここからは本題です。
APGのAccordion Pattern/Exampleを読み解いて、Reactの実装に落とし込んでいきます。
まずは、Accordion Pattern/Exampleの読みときます。
以下の流れで、必要なDOM要素(要素に対するWAIA-ARIA・)キーボード操作・アクセシビリティ操作を表にまとめていきます。
- 必要機能の概要を掴む
- 必要DOM要素をコンポーネント単位で表作成
- キーボード操作を表にまとめる
3.1. 必要機能の概要を掴む
参考セクション
- Pattern.About This Pattern
- Example.Example
Pattern.About This Pattern を要約してみましょう。
- アコーディオン : 1ページに複数のコンテンツセクションを表示するときに、スクロールの必要性を減らすために使用される。垂直に一連の見出しとパネルが表示される。ユーザーが見出しを選択すると、そのパネルが開閉される。
- Accordion Header : コンテンツのセクションを表すラベルまたはサムネイル。コンテンツのセクションを表示したり、実装によっては非表示にしたりするコントロールの役割も果たす。メニューボタン等が追加される場合もある。
- Accordion Panel : アコーディオンヘッダに関連付けられたコンテンツ。隠された状態でも、コンテンツのスニペットを視覚的に持続させる場合もある。
当たり前なことが書かれていますね。
Exampleも見てみましょう。
About This Patternに書かれていることが全て達成されていますね。
3.2. 必要DOM要素をコンポーネント単位で表作成
参考セクション
- Pattern.WAI-ARIA Roles, States, and Properties:
- Example.Example
- Example.Role, Property, State, and Tabindex Attributes
- Example.HTML Source Code
3.2.1. 前項やExampleから大まかな構造を読み取る
3.1.で、「Accordion Header」と「Accordion Panel」の2つが必要であることがわかりました。
Example.HTML Source Codeにそれらがあるか探してみましょう。
Example.HTML Source Code 原文
<div id="accordionGroup" class="accordion">
<h3>
<button type="button"
aria-expanded="true"
class="accordion-trigger"
aria-controls="sect1"
id="accordion1id">
<span class="accordion-title">
Personal Information
<span class="accordion-icon"></span>
</span>
</button>
</h3>
<div id="sect1"
role="region"
aria-labelledby="accordion1id"
class="accordion-panel">
<div>
<fieldset>
<p>
<label for="cufc1">
Name
<span aria-hidden="true">
*
</span>
:
</label>
<input type="text"
value=""
name="Name"
id="cufc1"
class="required"
aria-required="true">
</p>
<p>
<label for="cufc2">
Email
<span aria-hidden="true">
*
</span>
:
</label>
<input type="text"
value=""
name="Email"
id="cufc2"
aria-required="true">
</p>
<p>
<label for="cufc3">
Phone:
</label>
<input type="text"
value=""
name="Phone"
id="cufc3">
</p>
<p>
<label for="cufc4">
Extension:
</label>
<input type="text"
value=""
name="Ext"
id="cufc4">
</p>
<p>
<label for="cufc5">
Country:
</label>
<input type="text"
value=""
name="Country"
id="cufc5">
</p>
<p>
<label for="cufc6">
City/Province:
</label>
<input type="text"
value=""
name="City_Province"
id="cufc6">
</p>
</fieldset>
</div>
</div>
<h3>
<button type="button"
aria-expanded="false"
class="accordion-trigger"
aria-controls="sect2"
id="accordion2id">
<span class="accordion-title">
Billing Address
<span class="accordion-icon"></span>
</span>
</button>
</h3>
<div id="sect2"
role="region"
aria-labelledby="accordion2id"
class="accordion-panel"
hidden="">
<div>
<fieldset>
<p>
<label for="b-add1">
Address 1:
</label>
<input type="text"
name="b-add1"
id="b-add1">
</p>
<p>
<label for="b-add2">
Address 2:
</label>
<input type="text"
name="b-add2"
id="b-add2">
</p>
<p>
<label for="b-city">
City:
</label>
<input type="text"
name="b-city"
id="b-city">
</p>
<p>
<label for="b-state">
State:
</label>
<input type="text"
name="b-state"
id="b-state">
</p>
<p>
<label for="b-zip">
Zip Code:
</label>
<input type="text"
name="b-zip"
id="b-zip">
</p>
</fieldset>
</div>
</div>
<h3>
<button type="button"
aria-expanded="false"
class="accordion-trigger"
aria-controls="sect3"
id="accordion3id">
<span class="accordion-title">
Shipping Address
<span class="accordion-icon"></span>
</span>
</button>
</h3>
<div id="sect3"
role="region"
aria-labelledby="accordion3id"
class="accordion-panel"
hidden="">
<div>
<fieldset>
<p>
<label for="m-add1">
Address 1:
</label>
<input type="text"
name="m-add1"
id="m-add1">
</p>
<p>
<label for="m-add2">
Address 2:
</label>
<input type="text"
name="m-add2"
id="m-add2">
</p>
<p>
<label for="m-city">
City:
</label>
<input type="text"
name="m-city"
id="m-city">
</p>
<p>
<label for="m-state">
State:
</label>
<input type="text"
name="m-state"
id="m-state">
</p>
<p>
<label for="m-zip">
Zip Code:
</label>
<input type="text"
name="m-zip"
id="m-zip">
</p>
</fieldset>
</div>
</div>
</div>
2~13行目がAccordion Header、14~86行目がAccordion Panelになっていそうです。
<h3>
<button type="button"
aria-expanded="true"
class="accordion-trigger"
aria-controls="sect1"
id="accordion1id">
<span class="accordion-title">
Personal Information
<span class="accordion-icon"></span>
</span>
</button>
</h3>
<div id="sect1"
role="region"
aria-labelledby="accordion1id"
class="accordion-panel">
<div>
<!-- //... -->
</div>
</div>
また、1行目はHeaderとPanelを包む親要素になりそうです。
<div class="accordion">
<!-- HeaderとPanelが続く -->
</div>
3.3.2. 3.2.1の情報を元にDOM構成表を作成する
以下の形式でまとめていきます。
No. | コンポーネント名 | 要素(親) | attr | attr型 | 初期値 |
---|---|---|---|---|---|
1 | コンポーネント名 | divやpなど(親要素がある場合そのNo.) | role | アトリビュートのTypeScript型 | 初期値 |
3.2.1で
- AccordionGroup
- AccordionHeader
- AccordionPanel
の3コンポーネントで構成されていることがわかりました。
コンポーネントごとにアトリビュートをまとめていきます。
ですが、Example.Example はPatternページに記載されている内容を反映していない場合があります。(あくまでExampleなので!)
そのため、Pattern.WAI-ARIA Roles, States, and Properties:とExample.Role, Property, State, and Tabindex Attributesも参考にしながら、アトリビュートをまとめるようにしましょう。
AccordionGroup
No. | コンポーネント名 | 要素(親) | attr | attr型 | 初期値 |
---|---|---|---|---|---|
1 | AccordionGroup | div | class | string | - |
AccordionHeader
No. | コンポーネント名 | 要素(親) | attr | attr型 | 初期値 |
---|---|---|---|---|---|
2 | AccordionHeader | h3 (1) | - | - | - |
3 | 〃 | button (2) | aria-expanded | boolean |
false |
4 | 〃 | 〃 | aria-disabled | boolean |
false |
5 | 〃 | 〃 | aria-controls | string |
対応するAccordionPanelのID |
6 | 〃 | 〃 | id | string |
ユニークなID |
7 | 〃 | 〃 | class | string |
- |
WAI-ARIAが必要な理由は以下の通りです。
- No.2
Pattern.WAI-ARIA Roles, States, and Properties:第2項: ページの情報アーキテクチャに適したaria-level
の値を設定したrole heading
を持つ - No.3
Pattern.WAI-ARIA Roles, States, and Properties:第1項: ヘッダーのタイトルは、ロールボタンを持つ要素に含まれる
Pattern.WAI-ARIA Roles, States, and Properties:第3項: ヘッダに関連付けられているパネル要素が展開されているか折りたたまれているかを示す - Mo.4
Pattern.WAI-ARIA Roles, States, and Properties:第5項: ヘッダと関連付けられたパネルが表示され、アコーディオンがパネルの折りたたみを許可していない場合、ボタン要素にはaria-disabled
属性を設定する - No.5
Pattern.WAI-ARIA Roles, States, and Properties:第4項: アコーディオン・パネルの内容を含む要素のIDのaria-controls
AccordionPanel
No. | コンポーネント名 | 要素(親) | attr | attr型 | 初期値 |
---|---|---|---|---|---|
7 | AccordionPanel | div (1) | role | "region" |
- |
8 | 〃 | 〃 | aria-labelledby | string |
対応するAccordionHeaderのID |
9 | 〃 | 〃 | class | string |
- |
10 | 〃 | 〃 | hidden | boolean |
true |
WAI-ARIAが必要な理由は以下の通りです。
- No.7,8
Pattern.WAI-ARIA Roles, States, and Properties:第6項: 任意 パネル内容のコンテナとして機能する各要素は、パネルの表示を制御するボタンを参照する値を持つrole regionとaria-labelledbyを持つ
成果物
全てまとめたものはこちらになります。
No. | コンポーネント名 | 要素(親) | attr | attr型 | 初期値 |
---|---|---|---|---|---|
1 | AccordionGroup | div | class | string |
- |
2 | AccordionHeader | h3 (1) | - | - | - |
3 | 〃 | button (2) | aria-expanded | boolean |
false |
4 | 〃 | 〃 | aria-disabled | boolean |
false |
5 | 〃 | 〃 | aria-controls | string |
対応するAccordionPanelのID |
6 | 〃 | 〃 | id | string |
ユニークなID |
7 | 〃 | 〃 | class | string |
- |
8 | AccordionPanel | div (1) | role | "region" |
- |
9 | 〃 | 〃 | aria-labelledby | string |
対応するAccordionHeaderのID |
10 | 〃 | 〃 | class | string |
- |
11 | 〃 | 〃 | hidden | boolean |
true |
3.3. キーボード操作を操作表まとめる
参考セクション
- Pattern.Keyboard Interaction
- Example.Keyboard Support
- Example.JavaScript and CSS Source Code
- Example.HTML Source Code
Pattern.Keyboard Interaction と Example.Keyboard Support を参考に、キーボード操作を表にまとめていきます。
成果物
No. | キー | 操作内容 |
---|---|---|
1 | Enter or Space | フォーカスが折りたたまれたパネルのアコーディオンヘッダーにある場合 : 関連付けられたパネルを展開。 実装が1つのパネルのみ展開を許可する場合 : 他のパネルが展開されている場合はそのパネルを折りたたむ。 フォーカスが展開されたパネルのアコーディオンヘッダーにある場合 : パネルを折りたたむ(実装が折りたたみをサポートしている場合)。 |
2 | Tab | 次のフォーカス可能な要素にフォーカスを移動。 アコーディオン内のすべてのフォーカス可能な要素がページのTabシーケンスに含まれる。 |
3 | Shift + Tab | 前のフォーカス可能な要素にフォーカスを移動。 アコーディオン内のすべてのフォーカス可能な要素がページのTabシーケンスに含まれる。 |
4 | Down Arrow | フォーカスがアコーディオンヘッダーにある場合、 次のアコーディオンヘッダーにフォーカスを移動。フォーカスが最後のアコーディオンヘッダーにある場合、何もしないか、最初のアコーディオンヘッダーにフォーカスを移動。 |
5 | Up Arrow | フォーカスがアコーディオンヘッダーにある場合、 前のアコーディオンヘッダーにフォーカスを移動。フォーカスが最初のアコーディオンヘッダーにある場合、何もしないか、最後のアコーディオンヘッダーにフォーカスを移動。 |
6 | Home | フォーカスがアコーディオンヘッダーにある場合、 最初のアコーディオンヘッダーにフォーカスを移動。 |
7 | End | フォーカスがアコーディオンヘッダーにある場合、 最後のアコーディオンヘッダーにフォーカスを移動。 |
これで、全ての構成要素とアクセシビリティ用のアトリビュート、キーボード操作が把握できたかと思います!
5. まとめ
APGの読み方理解できたでしょうか?
今回は、アコーディオンのパターンを読み解き、アクセシビリティに関する仕様を理解しました。
APG を理解する流れを理解できれば、どんなガイドでも読めると思います!
次回は、Reactで実装していきます!
また今度!
Discussion