その他
ツールチップ
解説ありTooltip
フォーカス/ホバーで出る補足。aria-describedby とキーボード対応。
ツールチップとは?
ツールチップは、要素にフォーカスやホバーをしたとき、 その要素を補足説明する小さな吹き出しです。 アイコンボタンの意味や、入力欄の補足などに使われます。
注意点は、ツールチップは「補足」であって主要な情報ではないということ。 操作に必須の情報をツールチップだけに入れてはいけません。
なぜアクセシビリティが大事なの?
自作ツールチップや title 属性頼みは、次の問題を起こします。
- キーボード利用者に出ない。ホバー(マウス)でしか表示しないと、Tab でたどり着いても説明が読めません。
- タッチ利用者に出ない。スマホ等にはホバーがありません。
title属性は不安定。表示が遅く、スタイルできず、 支援技術での扱いも一定しません。- 消せない。WCAG では、出した補足は Esc で閉じられることが求められます。
解決策は、トリガーに aria-describedby でツールチップを関連付け、 フォーカスでもホバーでも表示し、Esc で閉じることです。
ライブデモ(推奨実装)
下のボタンには APG に沿ったツールチップが付いています。マウスのホバーだけでなく、Tab でフォーカスしても表示され、Esc で閉じられることを確認してください。
試してみよう:Tab でフォーカスすると吹き出しが出る → Esc で閉じる → マウスを乗せても出る。ツールチップ自体にはフォーカスが移らない。
ポイント
スクリーンリーダーでボタンにフォーカスすると「保存, ボタン, 変更内容をサーバーに保存します」のように、 名前のあとに説明(description)が読み上げられます。これが aria-describedby の効果です。
キーボード操作
| キー | 動作 | 必須/任意 |
|---|---|---|
| Tab | トリガーにフォーカス → ツールチップが表示される | 必須 |
| Esc | 表示中のツールチップを閉じる(フォーカスはトリガーに残す) | 必須 |
補足
ツールチップ自身はフォーカスを受け取りません(Tab の対象にしない)。 あくまでトリガーの補足なので、中にリンクやボタンを入れるのも避けます (操作要素を入れたいなら、それはツールチップではなくポップオーバー/ダイアログです)。
必要な WAI-ARIA / ロール
| 付ける場所 | 属性 / ロール | 意味 |
|---|---|---|
| トリガー | aria-describedby="ツールチップのid" | ツールチップを「説明」として関連付ける。フォーカス時に読み上げられる。 |
| ツールチップ本体 | role="tooltip" | これがツールチップであることを示す。 |
| ツールチップ本体 | hidden(非表示時) | 出ていない間は読み上げ・表示の対象外にする。 |
| トリガー | フォーカス可能な要素(<button> 等) | キーボードでも到達できるようにする。<div> 等にしない。 |
実装:推奨パターン(Good)
良い例 / 推奨
aria-describedby で関連付け、フォーカス・ホバー両方で表示し、Esc で閉じます。
マークアップ:
<!-- トリガーは必ずフォーカス可能な要素(button など) -->
<button type="button"
id="tip-trigger"
aria-describedby="tip-text">
保存
</button>
<!-- ツールチップ本体。自身はフォーカス不可、role="tooltip" -->
<div id="tip-text" role="tooltip" hidden>
変更内容をサーバーに保存します
</div>表示・非表示のスクリプト(focus と hover の両対応+Escが肝):
const trigger = document.getElementById('tip-trigger');
const tip = document.getElementById('tip-text');
if (trigger && tip) {
const show = () => { tip.hidden = false; };
const hide = () => { tip.hidden = true; };
// フォーカスでもホバーでも表示する(両対応が必須)
trigger.addEventListener('focus', show);
trigger.addEventListener('blur', hide);
trigger.addEventListener('mouseenter', show);
trigger.addEventListener('mouseleave', hide);
// Esc で閉じられるようにする
trigger.addEventListener('keydown', (e) => {
if (e.key === 'Escape') hide();
});
}補足
補足ですが、ツールチップの内容は「あれば便利」程度にとどめ、操作に不可欠な情報は本文やラベルに直接書くようにします。 出したツールチップは Esc で閉じられ、トリガーにマウスを移動しても消えないこと(WCAG 1.4.13)が理想です。
アンチパターン(Bad)
下のボタンは title 属性だけ、またはホバーでしか出ない自作ツールチップです。Tab でフォーカスしても説明が出ず、Esc でも閉じられません。
試してみよう:Tab でフォーカスしても説明が出ません(左:title はキーボードで出ない/右:ホバー専用)。タッチ端末でも出ず、Esc でも閉じられません。
<!-- ❌ アンチパターン1:title 属性だけ -->
<button type="button" title="変更内容を保存します">保存</button>
<!-- ❌ アンチパターン2:ホバーでしか出ない自作ツールチップ -->
<span class="has-tip">保存
<span class="tip">変更内容を保存します</span>
</span>悪い例 / 避ける
この実装の問題点:
- キーボードで出ない —
titleもホバー専用CSSも、Tab フォーカスでは表示されない。 - タッチで出ない — ホバーの無い端末では永遠に表示されない。
- 関連付けが無い —
aria-describedbyが無く、説明とトリガーが結びついていない。 - 閉じられない — Esc で閉じる手段がない(WCAG 1.4.13 違反)。
ポイント
title 属性は「無いよりマシ」ですが、表示が遅く・スタイルできず・キーボードで出ません。 補足説明をきちんと見せたいなら、aria-describedby + 自前の表示制御で実装しましょう。
実装チェックリスト
- トリガーはフォーカス可能な要素(
<button>等) - トリガーに
aria-describedbyでツールチップを関連付けている - ツールチップ本体に
role="tooltip"を付けている - フォーカスでもホバーでも表示される
- Esc で閉じられる(フォーカスはトリガーに残す)
- ツールチップ自身はフォーカスを受け取らない/操作要素を入れない
- 操作に必須の情報をツールチップ「だけ」に入れていない