15
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2023-11-30

はじめに


みなさんアクセシビリティを意識して開発できていますか?

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

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

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

アクセシビリティを意識したアコーディオンの仕様

⚪︎ アコーディオンとは?

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

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

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

⚪︎ キーボードインタラクション

  • 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タグ/>やネストされたアコーディオンを含む場合は、スクリーンリーダーのユーザーが構造を認識するのに役立つ

アクセシビリティを意識したアコーディオンの完成形

See the Pen Accordion Accessibility by でぐぅー | Qiita (@sp_degu) on CodePen.

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

では早速、アクセシビリティを意識したアコーディオンを実装して行きます。

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>
      ユーザー1
    </button>
    <button class="accordion-panel-item">
      <span class="material-symbols-outlined">person</span>
      ユーザー2
    </button>
    <button class="accordion-panel-item">
      <span class="material-symbols-outlined">person</span>
      ユーザー3
    </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では、他のコンポーネントにも焦点を当てて、アクセシビリティについても解説しているので、ぜひ購読していてください。


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

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

15
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?