【Vue + a11y】Vue3でモーダルのキーボードアクセシビリティ(a11y)の実装方法

## なぜキーボードアクセシビリティが重要なのか？
Web開発では、**モーダル(Modal)** を使って重要な情報やフォームを表示することがよくあります。しかし、モーダルが**キーボード操作に対応していない** 場合、以下のユーザーにとって使いづらくなります。

**・スクリーンリーダーを利用する視覚障がい者**
**・マウスを使えない身体障がい者**
**・キーボード操作を好むユーザー**

したがって、モーダルを実装する際には、**キーボードだけで操作可能なアクセシブルな設計（Keyboard Accessibility）** を考慮する必要があります。

この記事では、キーボード操作をサポート、Tabキーによるナビゲーション、Escキーによる閉じる、フォーカス管理などの機能が完備されたVueコンポーネントにモーダルを実装する方法を紹介します。

## キーボードアクセシビリティの主なポイント
モーダルウィンドウのキーボード対応を実装する際に考慮すべきポイントは次のとおりです。

**1. モーダルを開いたときに、最初のフォーカス可能な要素に自動フォーカス**
**2. Tabキーのフォーカスをモーダル内に閉じ込め、外に出ないようにする**
**3. Escキーを押すとモーダルを閉じ、元のフォーカス位置に戻す**

## Vueでキーボード対応のモーダルを実装
以下は、キーボードアクセシビリティを考慮したVueのモーダルコンポーネントの実装例です。

### 1. ```ref="modalContent"```
**実装ポイント**
・```ref="modalContent"``` は、```setup``` 内でモーダル（modal）のDOM要素を取得するために使用され、```watch```でキーボードイベントリスナーを追加することで、```Tab``` キーのフォーカス管理を実現
 ```
<template>

 <div
	class="slideup-modal__container"
	:class="{ view: isView, 'slideup-modal__gray': modalGray }"
	ref="modalContent"
>
```
### 2. 閉じるボタン(```button```)
**実装ポイント**
・```tab``` キーでフォーカスでき、Enter または Space でモーダルを閉じられる
・```alt="閉じる"``` でスクリーンリーダーが適切に読み上げる
```
<template>

<button class="slideup-modal__close" @click="closeModal()">
	<img src="btn_close.svg" alt="閉じる" />
</button>
```

### 3. ```Tab```キーのフォーカス制御
**実装ポイント**
・```Shift + Tab``` の場合、**最初の要素から最後の要素へループ**
・```Tab``` の場合、**最後の要素から最初の要素へループ**
・**モーダル外へフォーカスが移動するのを防ぐ**
```
<script>

const handleTabKey = (event) => {
	const firstElement = focusableElements[0]; // 最初のフォーカス可能な要素
	const lastElement = focusableElements[focusableElements.length - 1]; // 最後のフォーカス可能な要素

	if (event.key === 'Tab') {
		if (event.shiftKey) {
			// Shift + Tab で最初の要素にいる場合、最後の要素へフォーカス
			if (document.activeElement === firstElement) {
				event.preventDefault();
				lastElement.focus();
			}
		} else {
			// Tab で最後の要素にいる場合、最初の要素へフォーカス
			if (document.activeElement === lastElement) {
				event.preventDefault();
				firstElement.focus();
			}
		}
	}
};
```

### 4. ```Esc```キーでモーダルを閉じる
**実装ポイント**
・```Esc```を押すと**モーダルを閉じる**
・閉じた後、**元のボタンにフォーカスを戻す**
・これにより、ユーザーは次の操作をスムーズに行える

```
<script>

const closeModal = () => {
	isView.value = false;
	document.querySelector('body').style.overflow = 'auto';

	// モーダルを開いたときの要素にフォーカスを戻す
	if (props.calledElement) {
		props.calledElement.focus();
	}
};
```

### 5. ```keydown``` イベントを監視
**実装ポイント**
・モーダルを開いたときに、**最初のフォーカス可能な要素に自動フォーカス**
・keydown を監視し、**Tabキーのフォーカスをモーダル内に閉じ込める**
```
<script>

watch(
	() => props.childComponent,
	() => {
		nextTick(() => {
			if (modalArea) {
				modalArea.removeEventListener('keydown', handleTabKey);
			}
			if (lastElement) {
				lastElement.removeEventListener('keydown', handleLastkey);
			}

			// モーダルのDOM要素を取得
			modalArea = modalContent.value;

			if (modalArea) {
				// Tabキーのループ処理
				modalArea.addEventListener('keydown', handleTabKey);

				// フォーカス可能な要素を取得
				focusableElements = modalArea.querySelectorAll(
					'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
				);

				// 最初の要素に自動フォーカス
				if (focusableElements?.length > 0) {
					focusableElements[0].focus();

					lastElement = focusableElements[focusableElements.length - 1];

					lastElement.addEventListener('keydown', handleLastkey);
				}
			}
		});
	}
);
```

## まとめ
このVueコンポーネントを実装することで、以下のアクセシビリティ要件を満たすことができます。
**1. キーボードだけでモーダルを操作可能**
**2. Tabキーでモーダル内のフォーカスを適切に制御**
**3. Escキーでモーダルを閉じ、元のボタンにフォーカスを戻す**

**アクセシビリティ（a11y）** は **すべてのユーザーが快適にWebを利用できるようにするための重要な要素** です。

ぜひ、キーボード操作対応のモーダルを実装し、よりアクセシブルなWebを作りましょう！