この記事は『WAI-ARIA対応のアクセシブルなタブ型UI(基本編)』の続きです。
WAI-ARIAはアクセシビリティーの改善に役立つと先の記事で紹介しました。この記事ではWAI-ARIAに対応したReactでのタブのユーザーインタフェースを解説します。
サンプルをGitHubにアップしているので、デモとソースコードをご覧ください。
※本記事はViteで開発環境を用意し、React 19で説明しています。
Reactでのステート管理
実装の要素としてstate
に選択されたタブのIDを保持することとします。
▼ステート定義の部分の抜粋
// タブの選択状態をステート管理
const [state, setState] = useState<string>("1");
※コードは抜粋で掲載しているので、コピーする際はGitHubの「App.tsx」を参照ください。
HTMLの実装
HTMLの実装を紹介します。このコードはJSXで書いています。
▼JSXの部分の抜粋
<div>
<ul role="tablist">
{CONTENT_LIST.map((tab) => (
<li key={tab.id} role="presentation">
<button
role="tab"
id={toTabId(tab.id)}
aria-controls={toPanelId(tab.id)}
aria-selected={state === tab.id}
onClick={handleClick}
>
{tab.label}
</button>
</li>
))}
</ul>
{CONTENT_LIST.map((tab) => (
<div
key={tab.id}
role="tabpanel"
id={toPanelId(tab.id)}
aria-describedby={toTabId(tab.id)}
hidden={state !== tab.id}
>
{tab.content}
</div>
))}
</div>
HTMLコーディングのポイントとしては次の通りです。
①期待どおりに読み上げられるようにrole
属性を適切に利用します
タブとして機能するように、ul
要素にrole="tablist"
、 タブ部分となるbutton
要素にrole="tab"
、 パネル部分のdiv
要素にrole="tabpanel"
を追加します
<ul role="tablist"></ul>
<div role="tabpanel" …></div>
慣習にしたがってul>li
でマークアップしましたが、読み上げの支障となるのでli
タグにはrole="presentation"
を指定してます(もしかしたらタブUIにul>li
を使う必要はないかもしれません)
②タブ側のボタンはa
要素ではなくbutton
要素を使ってます。
<button role="tab" …></button>
macOS Safariだとa
タグはデフォルトでタブキーで操作できないためです。Safariでは次のように「Tabキーを押したときにウェブページ上の各項目を強調表示」を選択すると、a要素もタブキーで選択可能になります。ただ、このオプションを設定していなくてもタブキーで選択されたほうが望ましいと考えてのことです
JavaScriptとReactに絡んでくるポイント
- タブとなる
button
要素とパネルのdiv
要素の関連性を示すためaria-controls
属性を指定します。値は任意でid
属性を指定します button
要素にタブの選択状態を伝えるために、aria-selected
を真偽値で指定します- Reactのステートの値で動的とします。こうすれば半自動的に
aria-selected
属性が切り替わります
- Reactのステートの値で動的とします。こうすれば半自動的に
- パネル部分が表示・非表示の状態を伝えるために
hidden
属性を真偽値で指定します
またReactでは、id
属性を生成するためのuseId
フックを利用しています。useId
フックは各コンポーネントで一意のIDを生成するためのフックです。これを利用することで、タブボタンやタブパネルに固有のid属性を付与しやすくなります。
const id = useId();
タブボタンやタブパネルのid
属性は扱いやすくするために変換用の関数を用意しました。
const toPanelId = (panelId: string) => id + "-panel-" + panelId;
const toTabId = (tabId: string) => id + "-tab-" + tabId;
ボタン要素のイベントハンドラー
ボタン要素のイベントハンドラーのコードを紹介します。ボタンとパネルの紐付けは、意味的に合致している aria-controls
属性を利用してます。JavaScriptの制御が必要なものは独自の変数ではなく、可能な限り aria-*
属性で代替するのがベターなやり方と思います。なお、React Hooksを使って書いています。
▼イベントハンドラーの部分の抜粋
// タブの選択状態をステート管理
const [state, setState] = useState<string>("1");
// クリックしたときのイベントハンドラーです。
const handleClick = (event: React.MouseEvent) => {
// イベント発生源の要素を取得
const element = event.currentTarget;
// aria-controls 属性の値を取得
const tabState = element.getAttribute("aria-controls");
if (!tabState) {
return;
}
const tabId = tabState.split("-").pop();
if (!tabId) {
return;
}
// プロパティーを更新
setState(tabId);
};
CSSの実装
CSSはなるべく class
属性を使わず、aria-*
属性をセレクターとして指定しています。こうすれば、余計なクラス属性を増やす必要がなくなります。
/* UI制御のための指定 */
[aria-selected="true"] {
background-color: royalblue;
color: white;
}
※コードは抜粋で掲載しているので、コピーする際はGitHubの「App.css」を参照ください。
CSSの実装はCodeGridの記事「WAI-ARIAを活用したフロントエンド実装」で紹介されている「aria属性をCSSセレクターとして利用する」「独自に名前を付けるくらいなら、意味的に合致するaria属性を利用して、アクセシビリティーを確保しましょう」の提案をアイデアとしています。
まとめ
Reactで実装する場合はタブの状態はいずれかのstate
かprops
で管理しているはずです。その値を間借りしてaria-*
属性に適用すれば、簡単にアクセシビリティーを向上できます。これは一例に過ぎません。さまざまなユーザーインターフェイスに利用できるので応用くださいませ。
昨年の記事「脱jQueryのためにしたこと」でも紹介したように、これらのJSライブラリとWAI-ARIAの相性は抜群です。Reactユーザーがほんの少しWAI-ARIAの理解が進めば、簡単に利用できるでしょう。この記事によって、音声読み上げを求めているエンドユーザーへの配慮が少しでも進めばと考えています。
この方法はAngularやVue.jsでも実装できます。詳しくは次の記事を参照ください。
補足
記事を作成するにあたり複数のサンプルを用意して音声読み上げソフト(macOSの「VoiceOver」や「NVDA日本語版」)で検証しました。
※この記事が公開されたのは7年前ですが、今月5月に内容をメンテナンスしています。