【実装解説】WCAG 2.2に準拠した「音声読み上げ」の操作方法:要件、UI設計、コード例、チェックリストまで
先に要点(インバーテッドピラミッド)
- 必須の規格項目:自動再生音声には停止・一時停止・音量の操作を必ず用意(1.4.2 Audio Control)。視覚的な動きの自動更新は停止/非表示を提供(2.2.2 Pause, Stop, Hide)。ボタンやスライダーなどUI部品は名前・役割・値を機械可読に(4.1.2 Name, Role, Value)、状態メッセージはフォーカス移動なしで通知(4.1.3 Status Messages)。
- 2.2で新たに押さえる点:フォーカスが隠れない(2.4.11 AA)とフォーカスの見え方(2.4.13 AAA)で、読み上げコントロールの可視フォーカスを強化。ターゲット最小サイズ(2.5.8 AA, 24×24 CSS px)とドラッグ操作の代替(2.5.7 AA)も適用範囲です。
- UI原則:<button>に本物のボタン、<label>で明示ラベル、aria-pressed/aria-liveで状態・通知。キーボード操作と音量/速度の調整、一時停止/停止を確実に。
- サンプル実装:Web Speech API(SpeechSynthesis)でTTSを実装し、1.4.2/4.1.2/4.1.3/2.5.8に沿ったコントローラを提供。ブラウザ差はフォールバックで吸収します。
1. まず「WCAG 2.2で必要なこと」を地図化しましょう
音声読み上げの操作は、WCAGでは複数の達成基準にまたがります。最低限、次の5本柱を押さえると実装迷子になりません。
-
1.4.2 Audio Control(A)
ページで3秒を超えて自動再生される音声があるなら、停止・一時停止、またはシステム音量と独立の音量調整を提供すること。読み上げUI(TTS)を提供する場合も、まとめて明確な操作を出すのが安全です。 -
2.2.2 Pause, Stop, Hide(A)
自動で動く・更新される領域は停止/非表示を可能にします。読み上げ中にテキストが自動更新されるウィジェットがあるなら、更新の一時停止やライブ領域の制御が必要です。 -
4.1.2 Name, Role, Value(A)
ボタン・スライダー・セレクト等にプログラムで取得できる名前(accName)と役割、値や状態を持たせます(<button>
,<input type="range">
,aria-pressed
など)。 -
4.1.3 Status Messages(AA)
「読み上げ開始」「一時停止」などの状態通知は、フォーカスを動かさずにスクリーンリーダーへ伝えます(aria-live="polite"
など)。 -
2.5.7/2.5.8(AA)と2.4.11/2.4.13
- ドラッグ不要な操作も用意(2.5.7)。読み上げのシークはクリック/キー操作でも可能に。
- ターゲット最小サイズは24×24 CSS px(例外あり)。小さすぎる再生/停止ボタンはNGです(2.5.8)。
- フォーカスが隠れない(2.4.11)+見え方の強化(2.4.13AAA)。固定ヘッダーやダイアログにフォーカスが埋没しない設計・スタイルを。
なお、2.2で新設された基準の一覧はW3C公式の「What’s new」で確認できます(4.1.1 Parsingは削除など)。
2. UI設計の原則:スクリーンリーダー・キーボード・ポインタの三立て
**基本は「ネイティブ要素+見た目」**です。<div role="button">
より <button>
を。ARIAは足りないときだけ使います。WAI-ARIAのボタン・パターンが良い指針になります。
-
構成(推奨)
- 再生/一時停止(トグル):
<button aria-pressed="false">
- 停止:
<button>
(明示的に「最初から止める」) - 音量:
<input type="range" min="0" max="1" step="0.1">
+<label>
- 速度:
<input type="range" min="0.5" max="2" step="0.1">
+<label>
- 声種:
<select>
+<label>
(端末の音声リストから) - 状態通知:
<div aria-live="polite">
(「再生中 00:35」など)
- 再生/一時停止(トグル):
-
可視フォーカス
- フォーカスリングは隠さない/潰さない。必要に応じて太さ・コントラスト3:1以上を確保(2.4.13 AAAの考え方)。固定ヘッダー・スティッキー要素でフォーカスが隠れないように(2.4.11)。
-
ターゲットサイズ
- 最低 24×24 CSS px 相当を確保。密集する場合は十分なスペースで誤タップを防止(2.5.8)。
-
操作代替(ドラッグ不要)
- シークバーはクリック/矢印キーで移動できる実装に(2.5.7)。
3. コードサンプル:Web Speech APIで作る「読み上げコントローラ」
目的:1.4.2/2.2.2/4.1.2/4.1.3/2.5.8を押さえた最小構成。
備考:Web Speech API(SpeechSynthesis)はブラウザ互換に差があります。未対応環境のフォールバック(音声なしでもテキストが読める、外部TTSに渡す等)を用意してください。
<section aria-labelledby="tts-title">
<h2 id="tts-title">本文の音声読み上げ</h2>
<!-- コントローラ(24px以上のクリック領域+可視フォーカス) -->
<div role="group" aria-labelledby="controls-title" class="tts-controls">
<h3 id="controls-title" class="sr-only">読み上げコントロール</h3>
<button id="playPauseBtn" aria-pressed="false" aria-describedby="playPauseHint">
▶︎ 再生
</button>
<span id="playPauseHint" class="sr-only">もう一度押すと一時停止</span>
<button id="stopBtn">■ 停止</button>
<label for="volume">音量</label>
<input id="volume" type="range" min="0" max="1" step="0.1" value="1" />
<label for="rate">速度</label>
<input id="rate" type="range" min="0.5" max="2" step="0.1" value="1" />
<label for="voice">声</label>
<select id="voice" aria-describedby="voiceNote"></select>
<p id="voiceNote" class="sr-only">端末にある音声から選択</p>
</div>
<!-- 状態通知(フォーカス移動なしで読み上げ) -->
<p id="status" aria-live="polite" class="visually-noticeable">待機中</p>
<!-- 読み上げ対象 -->
<article id="readTarget">
<h3>サンプル本文</h3>
<p>ここに読み上げたいテキストが入ります。キーボードでも操作できます。</p>
</article>
</section>
<style>
.sr-only { position:absolute; left:-9999px; }
.tts-controls button, .tts-controls input, .tts-controls select {
min-width: 48px; min-height: 32px; /* 24×24以上を確保 */
margin: .25rem;
}
:focus { outline: 3px solid #1a73e8; outline-offset: 2px; } /* 視認しやすいフォーカス */
</style>
<script>
const textEl = document.getElementById('readTarget');
const statusEl = document.getElementById('status');
const playPauseBtn = document.getElementById('playPauseBtn');
const stopBtn = document.getElementById('stopBtn');
const volume = document.getElementById('volume');
const rate = document.getElementById('rate');
const voiceSel = document.getElementById('voice');
const synth = window.speechSynthesis;
let utterance = null;
let paused = false;
// 音声一覧の取得(環境依存)
function loadVoices() {
const voices = synth.getVoices();
voiceSel.innerHTML = '';
voices.forEach((v, i) => {
const opt = document.createElement('option');
opt.value = i;
opt.textContent = `${v.name} (${v.lang})`;
voiceSel.appendChild(opt);
});
}
loadVoices();
if (typeof speechSynthesis !== 'undefined') {
speechSynthesis.onvoiceschanged = loadVoices;
}
function announce(msg) {
statusEl.textContent = msg; // 4.1.3: フォーカス移動なしの通知
}
function createUtterance() {
const u = new SpeechSynthesisUtterance(textEl.innerText.trim());
const voices = synth.getVoices();
if (voices[voiceSel.value]) u.voice = voices[voiceSel.value];
u.volume = parseFloat(volume.value);
u.rate = parseFloat(rate.value);
u.onstart = () => announce('読み上げを開始しました');
u.onpause = () => announce('一時停止しました');
u.onresume = () => announce('再開しました');
u.onend = () => {
playPauseBtn.setAttribute('aria-pressed', 'false');
playPauseBtn.textContent = '▶︎ 再生';
paused = false;
announce('読み上げを終了しました');
};
return u;
}
playPauseBtn.addEventListener('click', () => {
const pressed = playPauseBtn.getAttribute('aria-pressed') === 'true';
// 既に発話中
if (synth.speaking && !paused) {
synth.pause(); paused = true;
playPauseBtn.textContent = '⏸ 再開';
playPauseBtn.setAttribute('aria-pressed', 'true');
return;
}
// 一時停止からの再開
if (paused) {
synth.resume(); paused = false;
playPauseBtn.textContent = '⏯ 一時停止';
playPauseBtn.setAttribute('aria-pressed', 'true');
return;
}
// 新規開始
utterance = createUtterance();
synth.cancel(); // 競合をクリア(2.2.2の意図にも合致)
synth.speak(utterance);
playPauseBtn.textContent = '⏯ 一時停止';
playPauseBtn.setAttribute('aria-pressed', 'true');
});
stopBtn.addEventListener('click', () => {
synth.cancel();
paused = false;
playPauseBtn.setAttribute('aria-pressed', 'false');
playPauseBtn.textContent = '▶︎ 再生';
announce('停止しました');
});
[volume, rate, voiceSel].forEach(el => {
el.addEventListener('input', () => {
if (synth.speaking) {
// 反映のためにいったん停止→再開(文中の更新は実装依存)
synth.cancel();
utterance = createUtterance();
synth.speak(utterance);
}
});
});
</script>
このサンプルが満たすポイント
- 1.4.2:停止/一時停止/音量の操作を提供。自動再生はせず、ユーザー起動時にのみ再生。
- 2.2.2:
cancel()
で競合する発話を停止し、望まない自動更新の中断を担保。 - 4.1.2:
<button>
,<label>
,<input type="range">
,<select>
で名前・役割・値が機械可読。トグルはaria-pressed
で状態を露出。 - 4.1.3:
aria-live="polite"
で**「開始/一時停止/終了」をフォーカス移動なし**に通知。 - 2.5.8:最小ターゲットサイズ24×24 CSS px以上を確保(CSS)。
- 2.5.7:シークをドラッグ以外(クリック・キー)で実装可能な設計方針。
- 2.4.11/2.4.13:可視フォーカスを太さ・コントラストで明確化し、隠れないように配慮。
補足:ブラウザによってはSpeechSynthesisの挙動・音声リストの扱いが異なります。MDNの互換表を参照し、未対応時は読み上げUIを非表示/グレイアウトするなどのフォールバックを。
4. 「自動再生音声」や「BGM」があるサイトでの注意点
- 自動再生の抑制:初期表示でBGMやナレーションが3秒を超えて鳴る設計は避けましょう。必要な場合は確実に停止・一時停止・音量を提供します(1.4.2)。スクリーンリーダー利用者にとって、音声の重なりは操作不能の原因になります。
- 動きの停止(2.2.2):字幕テロップやライブティッカーなど自動で動く領域は停止/非表示ができるUIを。読み上げ中に自動更新が割り込まないようライブ領域の粒度を見直します。
5. よく使うARIAと“落とし穴”
aria-live
の粒度:すべてをassertive
にすると情報洪水になります。読み上げの開始/停止はpolite
で十分なことが多いです(4.1.3)。- 偽ボタン問題:
<div role="button">
に頼るとキーボード/フォーカス/既定の可視リングが欠けがち。**ネイティブ<button>
**を基本に。 - ラベルの欠落:
<label for>
やaria-label
がないと4.1.2違反に直結。音量/速度/声種は必ず見えるラベルを。 - フォーカスの視認性不足:サイトデザインでフォーカスリングを消すのは禁物。色コントラストや太さで見やすく(2.4.13 AAAの趣旨)。
6. 受け入れテスト(手動+スクリーンリーダー)
キーボード
Tab/Shift+Tab
ですべての操作部品に到達できる。Enter/Space
で再生/一時停止/停止が動く。- スライダーは矢印/PageUp/Down/Home/Endで調整できる(ブラウザ標準)。
スクリーンリーダー
- NVDA/JAWS/VoiceOverで、「再生(ボタン)」「現在:一時停止」等の名前・役割・状態が正しく読まれる(4.1.2)。
- 再生・停止時に**「読み上げを開始/停止」などのステータスがフォーカス移動なし**でアナウンスされる(4.1.3)。
見た目・操作
- フォーカスリングが常に視認でき、固定ヘッダー等で隠れない(2.4.11)。
- ボタンのクリック領域が24×24 CSS px以上(2.5.8)。
- ドラッグ操作が必須になっていない(2.5.7)。
7. 実務チェックリスト(そのまま使える)
- [ ] 自動再生なし。やむを得ず自動再生する音声は停止/一時停止/独立音量を提供(1.4.2)。
- [ ] 読み上げの開始/停止をボタンで提供。**トグルは
aria-pressed
**で状態露出(4.1.2)。 - [ ] 状態メッセージは**
aria-live
**で通知し、フォーカスを動かさない(4.1.3)。 - [ ] ターゲットサイズは24×24 CSS px以上(2.5.8)。
- [ ] ドラッグの代替操作(クリック/キー)を用意(2.5.7)。
- [ ] フォーカスが隠れない・視認できる(2.4.11/2.4.13)。
- [ ] 更新/動きは停止/非表示が可能(2.2.2)。
8. 対象読者と効果(具体)
- Web/フロントエンド実装者:ネイティブ要素+最小限ARIAで、テスト負荷と不具合を減らせます。2.5.8/2.4.11はCSSだけでも対応できる箇所が多く、工数対効果が高いです。
- デザイナー:フォーカスの見え方(2.4.13 AAA)やターゲットサイズ(2.5.8 AA)をデザインガイドに織り込み、再発注コストを抑制。
- プロダクト/CS:自動再生・状態通知が適切になると離脱と問い合わせが減ります。特にBGM/ナレーションのある教育・観光サイトは1.4.2対応が効果的。
- 公共・教育機関:操作方法の明確化とステータス通知で、スクリーンリーダー利用者の利用体験が安定。審査・監査での指摘も減らせます(4.1.2/4.1.3)。
9. まとめ:ネイティブ要素+最小限ARIAで、“やさしい読み上げUI”に
- 1.4.2/2.2.2で音と動きのコントロールを確実に。4.1.2/4.1.3で状態と名前を機械可読に。新設の2.5.8/2.5.7/2.4.11で触りやすく/見つけやすく/隠れないUIに。
- 実装はシンプルに:
<button>
+<label>
+aria-live
。フォーカスの見え方と最小ターゲットサイズをCSSで担保すれば、WCAG 2.2準拠の大半は固まります。 - 次の一歩:自サイトの音声・動きを棚卸し→上のチェックリストで差分を洗い出し→サンプルコードをベースに導入。今日から、ひとつずつ整えていきましょう。
参考(主要ガイド)
- WCAG 2.2 本体/2.2の変更点。
- 1.4.2 Audio Control(理解文書)/2.2.2 Pause, Stop, Hide。
- 4.1.2 Name, Role, Value/4.1.3 Status Messages。
- 2.5.8 Target Size(最低24×24 CSS px)/2.5.7 Dragging Movements。
- 2.4.11 Focus Not Obscured(Minimum)/2.4.13 Focus Appearance。
- WAI-ARIA ボタン・パターン。