ノベルティメディア
mediaReactでアクセシブルなアコーディオンUIを実装する方法を解説
こんにちは。ノベルティの高宮です。
今回はアクセシブルなアコーディオンUIをReactで実装する方法について紹介します。
Reactを使ったWebサイト、WebアプリでアコーディオンUIを実装する方は、ぜひ参考にしてみてください。
今回のコラムでわかること
- アコーディオンUIでアクセシビリティを向上させる必要性
- ReactでアコーディオンUIを構築する方法
なぜアコーディオンUIのアクセシビリティを向上させる必要があるのか
Webサイトや、Webアプリでよく見るアコーディオンUIですが、アクセシビリティを何も考えずに実装した場合、以下のような問題点があります。
- エンターキーやスペースキーによるアコーディオンの開閉操作ができない
- サイト内単語検索でアコーディオンの中の単語にヒットしない
- スクリーンリーダーでアコーディオンの中の単語を読み上げられない
実際に見た目だけを実装したアコーディオンUIをキーボードで操作してみると
アコーディオンがフォーカスされずにに次の操作可能要素にフォーカスしてしまい、開閉できなくなっています。このような状態ではキーボードでしか操作ができないユーザーはアコーディオンの中身を見ることはできません。
より多くのユーザーがほしい情報にたどり着けるようにアクセシビリティを向上させる必要があります。
アコーディオンメニューの実装で気をつけるポイント
まず初めにアクセシビリティに配慮されたアコーディオンとはどのような点に気をつければ良いのかを挙げてから一つづつ解説していきます。
気をつけるポイント
- アコーディオンヘッダーはbutton要素でマークアップする。
- アコーディオンパネルが開いている場合、対応するアコーディオンヘッダーのaria-expanded属性をtrueにする。
- アコーディオンヘッダーのaria-controls属性にアコーディオンパネルのid指定する
- アコーディオンヘッダーに画像やアイコンを使う場合、aria-hidden=”true”にする
- Enter/Spaceキーで開閉できる,Tab/Shift + Tabでフォーカスを次/前のフォーカス可能な要素に移動できる。
1.アコーディオンヘッダーはbutton要素でマークアップする。
アコーディオンUIはマウス、キーボードで開閉ができなければなりません。よく使われるdiv要素やp要素は操作可能な要素ではないのでここでの使用は控えましょう。
button要素はマウス、キーボード、指、音声コマンド、その他の支援技術で起動することができる操作可能要素なのでbutton要素を使ってマークアップします。
button要素を使うことでスクリーンリーダーでも「ボタン」と読み上げてくれるので現在フォーカスしている要素がクリックできる要素であると伝えることができます。
2.アコーディオンパネルが開いている場合、対応するアコーディオンヘッダーのaria-expanded属性をtrueにする。
このaria-expanded属性は、アコーディオンの開閉状態を示すために使用されます。aria-expanded="true"が設定されている場合、対応しているアコーディオンパネルが開いていることを支援機器に伝えることができます。
<div className="Accordion_item">
<button
id="accordion-0-button"
className="Accordion_header"
aria-expanded="true" //開いている状態ではtrueにする
aria-controls="accordion-0-content"
>
朝ごはんは食べましたか?
<span className="Accordion_icon Accordion_isOpen"></span>
</button>
<div
id="accordion-0-content"
className="Accordion_content"
>
<p>はい。食べました。</p>
</div>
</div>
3.アコーディオンヘッダーのaria-controls属性にアコーディオンパネルのid指定する
aria-controls属性は直接関連するコンテンツのエリアを参照するために使用されます。今回の例でいうと、アコーディオンヘッダーがどのアコーディオンパネルに関連しているのかを参照するために指定します。
このように指定することで支援技術はヘッダーと関連するパネルの間の関係を理解し、ユーザーに適切な情報を提供することができます。
<div className="Accordion_item">
<button
id="accordion-0-button"
className="Accordion_header"
aria-expanded="true"
aria-controls="accordion-0-content" //アコーディオンパネルのidを指定する
>
朝ごはんは食べましたか?
<span className="Accordion_icon Accordion_isOpen"></span>
</button>
<div
id="accordion-0-content"
className="Accordion_content"
>
<p>はい。食べました。</p>
</div>
</div>
4.アコーディオンヘッダーに画像やアイコンを使う場合、aria-hidden=”true”にする
aria-hidden=”true”を指定すると視覚的には存在させたまま、スクリーンリーダーでは読み取らない要素にすることができます。
アコーディオンUIには開閉状態を視覚的に表す矢印のアイコンやプラスマークのアイコンなどが用いられることがほとんどです。もしそのアイコンをSVGやimg要素で実装する場合はaria-hidden=”true”を指定することでスクリーンリーダーに読み上げられないようにしましょう。
<div className="Accordion_item">
<button
id="accordion-0-button"
className="Accordion_header"
aria-expanded="true"
aria-controls="accordion-0-content"
>
朝ごはんは食べましたか?
<span className="Accordion_icon Accordion_isOpen" aria-hidden="true">
<SVGIcon />
</span>
//aria-hidden="true"で表示されないようにする
</button>
<div
id="accordion-0-content"
className="Accordion_content"
>
<p>はい。食べました。</p>
</div>
</div>;
5.Enter/Spaceキーで開閉できる,Tab/Shift + Tabでフォーカスを次/前のフォーカス可能な要素に移動できる。
これらはアコーディオンヘッダーをbutton要素でマークアップしていれば特別な処理をいれなくても実現されているかと思います。なので一通りの実装が完了したら、これらが実現できているかのテストをしましょう。
完成形はこちら
上記のポイントを踏まえてアコーディオンUIを実装しました。
import React, { FC, useState, useRef, useEffect } from "react";
import styles from "./Accordion.module.scss";
interface AccordionItemProps {
title: string;
answer: string;
index: number;
}
const AccordionItem: FC<AccordionItemProps> = ({ title, answer, index }) => {
const [isOpen, setIsOpen] = useState(false);
const [height, setHeight] = useState("0px");
const contentRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (contentRef.current) {
setHeight(isOpen ? `${contentRef.current.scrollHeight}px` : "0px");
}
}, [isOpen]);
const idPrefix = `accordion-${index}`;
return (
<div className={styles.item}>
<button
id={`${idPrefix}-button`}
className={styles.header}
onClick={() => setIsOpen(!isOpen)}
aria-expanded={isOpen}
aria-controls={`${idPrefix}-content`}
>
{title}
<span
className={`${styles.icon} ${isOpen && styles.isOpen}`}
></span>
</button>
<div
id={`${idPrefix}-content`}
ref={contentRef}
className={styles.content}
style={{ maxHeight: `${height}` }}
>
<p>{answer}</p>
</div>
</div>
);
};
const Accordion: FC = () => {
const questionsAnswers = [
{ title: "朝ごはんは食べましたか?", answer: "はい。食べました。" },
{
title: "昨日の夜ご飯は何を食べましたか?",
answer: "忘れてしまいました。",
},
{ title: "今日の昼食は何を食べますか?", answer: "餃子を食べます。" },
];
return (
<div className={styles.container}>
{questionsAnswers.map((qa, index) => (
<AccordionItem
key={index}
title={qa.title}
answer={qa.answer}
index={index}
/>
))}
</div>
);
};
export default Accordion;
実際にキーボードのみの操作やスクリーンリーダーでの読み込みも問題なく実装することができました。
まとめ
今回はアクセシビリティに配慮したアコーディオンUIを実装してみました。
アコーディオンUI以外にも、モーダルウィンドウやタブの切替などのUIも一見単純に見えますが、実際は考慮しなければいけない点が多いです。見た目を実装するだけでなく、しっかりと調査してより多くの人が操作できるUIの実現を目指しましょう。
ノベルティではアクセシビリティ、ユーザビリティに配慮した実装やJamstackでのサイト制作にも力をいれていますので、ぜひお任せください!
興味のある方はお問い合わせを!
↓ この記事をご覧の方へおすすめの記事はこちら ↓
おすすめ記事/ PICKUP
記事カテゴリー/ CATEGORY
企業の課題はノベルティひとつで完結
ホームページ制作などのWeb制作をはじめ、
システム開発やマーケティング支援などワンストップで対応
まずはお気軽にお問い合わせください
お電話またはメールでお気軽にお問い合わせください。
各種サービスの資料をご用意しています