【アクセシビリティ】アクセシビリティを意識したアコーディオンの作り方

## はじめに
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390720/2c25cbed-ad46-82b3-bcab-bd38fecb3094.png)
みなさんアクセシビリティを意識して開発できていますか？

必要なところに`role属性`を記述したり、`tabキー`でフォーカスができるようにしたりなど、意識しないといけないことも多いです。
そのため、アクセシビリティを完璧にやろうとするのは一苦労です。

ただ、コンポーネントごとに区切って、アクセシビリティを理解しておけば、実装するタイミングに思い出しやすく、アクセシビリティも意識しやすいと思います。

そのため、この記事では「**アコーディオン**」に焦点を当てて、アクセシビリティを意識したアコーディオンの実装方法とアコーディオンで意識した方がいいアクセシビリティを解説しようと思います。

## アクセシビリティを意識したアコーディオンの仕様
### ⚪︎ アコーディオンとは？
アコーディオンは、縦に積み重ねられたクリック可能な見出しの要素です。
見出しは、コンテンツを表示/非表示にするためのコントロールとして機能します。
また、アコーディオンは、複数のセクションを 1ページに表示することができ、スクロール量を減らすことができます。

アコーディオンには以下の2つに部分に分かれています。

+ **Accordion Header**
  + コンテンツのセクションを表すラベル
  + Accordion Panelを表示/非表示するためのボタンとして機能する
+ **Accordion Panel**
  + Accordion Headerに関連するコンテンツのセクション

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390720/0be593e0-ecfa-ba9a-e21e-1556fe379055.png)


### ⚪︎ キーボードインタラクション
+ **`Enterキー` ・ `Spaceキー`**
  + 折り畳まれたAccordion PanelのAccordion Headerにフォーカスがある場合
    + → Accordion Panelを展開する
    + → 他のAccordion Panelが展開されていたら、他のAccordion Panelを折りたたむ
  + 展開されたAccordion PanelのAccordion Headerにフォーカスがある場合
    + → 他のAccordion Panelを折りたたむ
+ **`Tabキー`**
  + 次のフォーカス可能な要素に移動する
    + → Accordion Panelが展開されていたら、Accordion Panel内のフォーカス可能な要素に移動する
    + → Accordion Panelが折り畳まれていたら、次のAccordion Headerにフォーカスが移動する
+ **`Shiftキー + Tabキー`**
  + 前のフォーカス可能な要素に移動する
    + → Accordion Panelが展開されていたら、Accordion Panel内のフォーカス可能な要素に移動する
    + → Accordion Panelが折り畳まれていたら、前のAccordion Headerにフォーカスが移動する
+ **`↓キー`**（任意）
  + フォーカスがAccordion Headerにある場合
    + → フォーカス可能な次のAccordion Headerに移動する
  + フォーカスが最後のAccordion Headerにある場合
    + → 何もしない or 最初のAccordion Headerに移動する
+ **`↑キー`**（任意）
  + フォーカスがAccordion Headerにある場合
    + → フォーカス可能な前のAccordion Headerに移動する
  + フォーカスが最初のAccordion Headerにある場合
    + → 何もしない or 最後のAccordion Headerに移動する
+ **`Homeキー`**（任意）
  + フォーカスがAccordion Headerにある場合
    + → 最初のAccordion Headerに移動する
+ **`Endキー`**（任意）
  + フォーカスがAccordion Headerにある場合
    + → 最後のAccordion Headerに移動する

### ⚪︎ WAI-ARIA の役割、状態、プロパティ

+ Accordion Header は、`role="button"`の要素を含める
+ Accordion Header のボタンは、ページの情報アーキテクチャに適した `aria-level`の値を設定した `role="heading"`を持つ要素でラップする
+ Accordion Header に関連付くAccordion Panelが展開されている場合、headerのbutton要素に`aria-expanded="true"`を設定する
+ Accordion Header に関連付くAccordion Panelが折り畳まれている場合、headerのbutton要素に`aria-expanded="false"`を設定する
+ Accordion Header のボタン要素には、Accordion Panelの要素のIDを設定する
+ Accordion Header と関連付くAccordion Panelが展開され、アコーディオンが折りたたみを許可しない時、headerのbutton要素に`aria-disabled="true"`を設定する
+ 【任意】Accordion PanelとAccordion Panel内の各要素には、Accordion Headerのbutton要素を参照する`role="region"`と`aria-lebelledby`を設定する
  + 同時で展開可能な Accordion Panel が6つ以上含むアコーディオンには、ランドマーク領域が増殖するような状況での`role="region"`の使用は避ける
  + `role="region"`は、Accordion Panelに `<Hタグ/>`やネストされたアコーディオンを含む場合は、スクリーンリーダーのユーザーが構造を認識するのに役立つ


## アクセシビリティを意識したアコーディオンの完成形
<p class="codepen" data-height="500" data-default-tab="result" data-slug-hash="PoXvwyw" data-editable="true" data-user="sp_degu" style="height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/sp_degu/pen/PoXvwyw">
  Accordion Accessibility</a> by でぐぅー | Qiita (<a href="https://codepen.io/sp_degu">@sp_degu</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## アクセシビリティを意識したアコーディオンの作り方
では早速、アクセシビリティを意識したアコーディオンを実装して行きます。
### 1. HTMLを実装する
```sample.html
<div class="accordion">

  <h3 class="accordion-header">
    <button class="accordion-trigger" aria-expanded="false" aria-controls="sect1" id="accordion1id">
      ユーザー
      <span class="material-symbols-outlined chevron_right">chevron_right</span>
    </button>
  </h3>
  
  <div class="accordion-panel" id="sect1" aria-labelledby="accordion1id" role="region" hidden>
    <button class="accordion-panel-item">
      <span class="material-symbols-outlined">person</span>
      ユーザー１
    </button>
    <button class="accordion-panel-item">
      <span class="material-symbols-outlined">person</span>
      ユーザー２
    </button>
    <button class="accordion-panel-item">
      <span class="material-symbols-outlined">person</span>
      ユーザー３
    </button>
  </div>
 
  <!-- Accordion HeaderとAccordion Panelが繰り返される-->
</div>
```

### 2. CSSを実装する
```sample.css


/*　以下スタイル調整　*/
body {
  background-color: #212529;
  color: #fff;
  display: grid;
  height: calc(100vh - 40px);
  margin: 0;
  padding: 20px 0;
  place-items: center;
  width: 100vw;
}

.accordion {
  background-color: rgb(128 128 128 / .3);
  border-radius: 24px;
  padding: 16px;
  position: relative;
  min-width: 300px;
  background-blend-mode: luminosity;
  backdrop-filter: blur(50px);
}

.accordion::before {
  background: linear-gradient(135deg, rgb(255 255 255 / .4) 0, rgb(255 255 255 / 0) 40%, rgb(255 255 255 / 0) 60%, rgb(255 255 255 / .1) 100%);
  border: 1.4px solid transparent;
  border-radius: 24px;
  content: "";
  inset: 0;
  position: absolute;
  -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0) border-box;
  -webkit-mask-composite: destination-out;
  mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0) border-box;
  mask-composite: exclude;
  z-index: -1;
}

.accordion-header {
  margin: 0;
}

button {
  align-items: center;
  background: none;
  border: none;
  color: #ffffff;
  display: flex;
}

.accordion-trigger {
  font-size: 16px;
  font-weight: bold;
  justify-content: space-between;
  padding: 16px 16px 12px;
  cursor: pointer;
  width: 100%;
}

.accordion-panel-item {
  cursor: pointer;
  gap: 8px;
  margin: 0 8px;
  padding: 8px;
  width: calc(100% - 16px);
}

.accordion-panel-item:hover {
  border-radius: 8px;
  background: radial-gradient(34.12% 136.61% at 50% 100%, rgba(94, 94, 94, 0.14) 0%, rgba(94, 94, 94, 0.00) 73.85%), radial-gradient(50% 164.29% at 50% 100%, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0.00) 60.33%), linear-gradient(0deg, rgba(94, 94, 94, 0.18) 0%, rgba(94, 94, 94, 0.18) 100%), rgba(255, 255, 255, 0.06);
  background-blend-mode: color-dodge, normal, color-dodge, lighten;
}

.material-symbols-outlined {
  font-variation-settings:
  'FILL' 1,
  'wght' 400,
  'GRAD' 0,
  'opsz' 24
}
```

### 3. JavaScriptを実装する
```sample.js
class Accordion {
  constructor(domNode) {
    this.rootEl = domNode;
    this.buttonEl = this.rootEl.querySelector('button[aria-expanded]');
    const controlsId = this.buttonEl.getAttribute('aria-controls');
    this.contentEl = document.getElementById(controlsId);
    this.open = this.buttonEl.getAttribute('aria-expanded') === 'true';
    this.buttonEl.addEventListener('click', this.onButtonClick.bind(this));
  }

  onButtonClick() {
    this.toggle(!this.open);
  }

  toggle(open) {
    if (open === this.open) {
      return;
    }

    this.open = open;

    this.buttonEl.setAttribute('aria-expanded', `${open}`);

    if (open) {
      this.contentEl.removeAttribute('hidden');
      this.buttonEl.lastElementChild.innerText = "expand_more";
    } else {
      this.contentEl.setAttribute('hidden', '');
      this.buttonEl.lastElementChild.innerText = "chevron_right";
    }
  }

  open() {
    this.toggle(true);    
  }

  close() {
    this.toggle(false);
  }
}

const accordions = document.querySelectorAll('.accordion h3');

accordions.forEach((accordionEl) => {
  new Accordion(accordionEl);
});
```

## まとめ
この記事では、「**アコーディオン**」に焦点を当てて、アクセシビリティを意識したアコーディオンの実装方法とアコーディオンで意識した方がいいアクセシビリティを解説しました。

ぜひこの記事をストックして、アコーディオンを実装する時にアクセシビリティについて思い出してもらえると嬉しいです。

Advent Calendar 2023では、他のコンポーネントにも焦点を当てて、アクセシビリティについても解説しているので、ぜひ購読していてください。

https://qiita.com/advent-calendar/2023/degudegu2510

---

最後まで読んでくださってありがとうございます！

普段はデザインやフロントエンドを中心にQiitaに記事を投稿しているので、ぜひQiitaのフォローとX(Twitter)のフォローをお願いします。

https://twitter.com/degudegu2510