その他
ツールチップ
解説ありTooltip
フォーカス/ホバーで出る補足。aria-describedby とキーボード対応。
仕様が更新されました
- 機能追加#5175(新しいタブで開きます)1.4.13: 「消せること」の解説を書き直し、2つの例外を明確化
検討中の仕様変更があります
- #3431(新しいタブで開きます)Tooltip: role="tooltip" が name-prohibited に変更予定
ツールチップとは?
ツールチップは、要素にフォーカスやホバーをしたとき、 その要素を補足説明する小さな吹き出しです。 アイコンボタンの意味や、入力欄の補足などに使われます。
注意点は、ツールチップは「補足」であって主要な情報ではないということ。 操作に必須の情報をツールチップだけに入れてはいけません。
なぜアクセシビリティが大事なの?
自作ツールチップや 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 で閉じられる(フォーカスはトリガーに残す)
- ツールチップ自身はフォーカスを受け取らない/操作要素を入れない
- 操作に必須の情報をツールチップ「だけ」に入れていない
現場の実例 — APG と実装の違い
role="tooltip" と aria-describedby を付けず「視覚専用のヒント」と割り切る
- APG では
- ツールチップ本体に role="tooltip" を付け、トリガーから aria-describedby で参照する。ホバーとフォーカスの両方で表示し、Esc で閉じられるようにする。タッチ入力については言及がない。
- 現場では
- Base UI の Tooltip は role="tooltip" も aria-describedby も付けず、ドキュメントで「ツールチップは視覚的な補助であり、必須情報はトリガーの aria-label 等で直接提供すべき」と明言。さらにタッチデバイスでは表示自体を無効化し、必須情報には Popover(openOnHover)を使うよう誘導している。Material UI もタッチでの誤発火防止に enterTouchDelay(デフォルト 700ms の長押し)を設ける。
- なぜ
- ツールチップは hover / focus 依存で設計されているが、タッチデバイスにはどちらも存在しない(タップは即アクティベートになる)。つまり「ツールチップにしか無い情報」は一部のユーザーに構造的に届かない。Sarah Higley が 2019 年にこの非互換を指摘して以来仕様側は未解決のままで、APG に忠実な Radix には「タッチで開かない」issue が繰り返し報告されている。Base UI チームはこの構造問題を踏まえ、「トリガーの目的が開閉自体か否か」という判断基準を明文化し、ツールチップを冗長な視覚ヒントに限定する設計判断をした。
採用:Base UI、Material UI
出典:Base UI — Tooltip(Accessibility guidelines)(新しいタブで開きます)、Sarah Higley — Tooltips in the time of WCAG 2.1(新しいタブで開きます)、MUI — Tooltip(新しいタブで開きます)
Ariakit は aria-describedby ではなく aria-labelledby をデフォルトにした
- APG では
- トリガー要素はツールチップを aria-describedby で参照する。ツールチップは補足説明であり、アクセシブルネームの提供元ではないという前提。
- 現場では
- Ariakit は v2 でツールチップのデフォルト関連付けを aria-labelledby に変更した。説明として使いたい場合のみ、利用側が明示的に aria-describedby へ切り替える設計になっている。
- なぜ
- Web の実態として「アイコンのみでラベルの無いボタン + ツールチップで名前を示す」実装が非常に多い。この使い方で aria-describedby にするとボタンにアクセシブルネームが無く、実機検証では VoiceOver が「Undo, button, Undo」と重複読みし、NVDA と読み上げが一貫しないことをメンテナが確認した。aria-labelledby なら常に安定して読み上げられるため、APG の規定より「実際の使われ方」を優先した。なお本来は role="tooltip" 自体を外すべきという議論も残っており、チーム自身が仕様との差分を認識した上での選択。
採用:Ariakit
出典:Ariakit — Tooltip(新しいタブで開きます)、ariakit/ariakit#2228 — Tooltip accessibility(新しいタブで開きます)
APG の tooltip パターン自体が「合意未了」のまま — 現場は toggletip へ
- APG では
- APG のツールチップパターンには「work in progress; タスクフォースの合意に至っていない」と明記されており、2016 年から未解決のまま。クリックで開閉する toggletip は APG のパターン一覧に存在しない。
- 現場では
- Sarah Higley は role="tooltip" を「ロールの中の愛されない子(unloved child of roles)」と呼び、必須でない補足情報にのみツールチップを使うよう推奨。Heydon Pickering はタッチ・マウス・キーボードすべてで動く toggletip(クリックで開閉する説明ボックス)を代替として提唱している。
- なぜ
- hover / focus 依存という構造的欠陥に加え、WCAG 2.1 の 1.4.13(ホバー又はフォーカスで表示されるコンテンツ)が dismissable / hoverable / persistent の3要件を課したことで、APG 型ツールチップの実装コストは大きく跳ね上がった。W3C ARIA WG 自身も issue やTPAC 2023 の議論で「現行の tooltip の定義ではリッチなコンテンツを含むユースケースを表現しきれない」とロールの見直しが必要と認めている。ただし toggletip にも「本質的な情報を小さなボタンの裏に隠すべきではない」という批判があり、現場でも完全なコンセンサスではなく有力な代替案の一つという位置づけ。
採用:Sarah Higley、Heydon Pickering (Inclusive Components)
出典:Sarah Higley — Tooltips in the time of WCAG 2.1(新しいタブで開きます)、Inclusive Components — Tooltips & Toggletips(新しいタブで開きます)、w3c/aria-practices#128 — Tooltip pattern consensus(新しいタブで開きます)