【基礎編】W3Cが書いたアクセシビリティガイド を読んで アクセシビリティ準拠アコーディオン を React で実装しよう!

2024/11/06に公開

概要

この記事では、W3Cが提供する ARIA Authoring Practices Guide(以下APG)を読み解き、React でアクセシビリティに考慮したアコーディオンを実装してみます。

基礎編では、APGを読み解き、アコーディオンに関するアクセシビリティ要件を理解することが目的です。

この記事(基礎編/実装編)を読むことで得られること
✅ APG とは何か
✅ APG を理解する流れ
✅ Accordion のアクセシビリティに関する仕様
✅ APG に基づき、自力でアクセシビリティを担保したUIを実装する方法
🔶 アクセシビリティの基本
🔶 WAI-ARIA について
❌ 実稼働環境で即利用可能レベルのコード(APGは実稼働環境向けではないと明言している[1]

この記事(基礎編/実装編)の流れ

  1. ARIA Authoring Practices Guide (APG) とは?
  2. APG を理解する
  3. Accordion Pattern/Example を読み解いてみる
    --- 今回はここまで ---
  4. React環境構築
  5. Accordion Group を実装する
  6. Accordion Header を実装する
  7. Accordion Panel を実装する
  8. 動かしてみる

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プロジェクトによって実行された相互運用性テストの実用的な概要が記載されています。
このセクションの読み方は、以下にわかりやすく記載されています。
https://www.w3.org/WAI/ARIA/apg/about/at-support-tables/

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の読みときます。
https://www.w3.org/WAI/ARIA/apg/patterns/accordion/
https://www.w3.org/WAI/ARIA/apg/patterns/accordion/examples/accordion/

以下の流れで、必要なDOM要素(要素に対するWAIA-ARIA・)キーボード操作・アクセシビリティ操作を表にまとめていきます。

  1. 必要機能の概要を掴む
  2. 必要DOM要素をコンポーネント単位で表作成
  3. キーボード操作を表にまとめる

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が必要な理由は以下の通りです。

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 InteractionExample.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で実装していきます!

また今度!

脚注
  1. Introduction - APG is Not a Normative Standard ↩︎

GitHubで編集を提案

Discussion