WCAG 達成基準 · Level AA

WCAG 1.4.13: ホバーまたはフォーカス時のコンテンツ

WCAG 1.4.13 は、ポインタのホバーやキーボードフォーカスによって表示される追加コンテンツが、閉じることができ、ホバー可能で、かつ持続することを求めています。これにより、ロービジョンのユーザー、運動障害のあるユーザー、認知障害のあるユーザーが、ツールチップ形式のコンテンツに予期せず失うことなくアクセスし、操作できるようにしています。

この規定が意味すること

WCAG 1.4.13 は、ウェブ上でよく見られるインタラクションパターン、つまりユーザーが要素の上にポインタをホバーしたり、キーボードフォーカスを移動したときにコンテンツが表示されるパターンを扱います。これにはツールチップ、サブメニュー、カスタムドロップダウンのヒント、日付ピッカーのポップオーバー、その他ホバーやフォーカスイベントに応じて表示されるあらゆるオーバーレイが含まれます。この達成基準は、そのようなコンテンツがブラウザによってネイティブに制御されていない場合(たとえばネイティブの title 属性によるツールチップは対象外)に適用され、同時に満たさなければならない3つの中核要件を定めています。

閉じることができる(Dismissible): ユーザーはポインタフォーカスやキーボードフォーカスを移動させることなく、追加コンテンツを閉じることができなければなりません。標準的な手段は Escape キーを押すことです。これにより、ユーザーが解消できない形でオーバーレイが他のページコンテンツを覆い隠してしまうことを防ぎます。特に、画面を拡大していて簡単に別の場所を見られないユーザーにとって重要です。

ホバー可能(Hoverable): 追加コンテンツがユーザーのホバーによって表示された場合、ユーザーはポインタを新たに表示されたコンテンツの上に移動しても、それが消えないようにしなければなりません。トリガー要素からカーソルが離れた瞬間にツールチップが消えてしまうと、ユーザーは長い内容を読んだり、そのテキストをコピーしたり、その中のリンクやコントロールを操作したりできません。

持続する(Persistent): 追加コンテンツは、ホバーまたはフォーカスのトリガーが解除されるか、ユーザーがそれを閉じる(たとえば Escape によって)か、情報が有効でなくなるまで表示されたままでなければなりません。ユーザーのポインタやフォーカスがトリガーまたはオーバーレイ自体の上にある間は、タイマーや任意の遅延によってコンテンツが消えてはなりません。

合格とするには、これら3つの条件すべてが満たされている必要があります。いずれか1つでも欠けていれば不合格です。たとえば、ポインタがトリガーからツールチップに向かって移動したときにツールチップが消えてしまうもの(ホバー可能でない)、3秒後に自動的に閉じてしまうもの(持続しない)、フォーカスを移動しないと閉じられないもの(閉じることができない)などです。WCAG が設けている唯一の公式な例外は、追加コンテンツの視覚的な提示が完全にユーザーエージェントによって制御されている場合であり、title 属性だけで生成されるブラウザネイティブのツールチップはこのカテゴリに該当し、対象外となります。ただし、それ自体にアクセシビリティ上の制限はあります。

なぜ重要か

この達成基準は主に、標準的なマウスやキーボード操作の制御が難しいユーザー、画面拡大に依存するユーザー、情報処理に時間がかかるユーザーに恩恵をもたらします。誰が影響を受けるのかを理解することで、チームは修正の優先度を適切に判断できます。

ロービジョンのユーザー は、ZoomText や OS 標準の拡大鏡などの画面拡大ソフトを使用することが多く、高いズームレベルでは画面の一部しか見えません。ツールチップが表示されたとき、その一部が画面外にある場合があり、ユーザーはそこへパンしなければなりません。もしツールチップが、ポインタがトリガーから離れた瞬間に消えてしまうと、ユーザーはパンして読むことができません。世界保健機関によると、世界で約 22 億人が何らかの視覚障害を抱えており、そのうちコンピュータを使用する人の相当数がスクリーンリーダーではなく拡大機能に依存しています。

運動障害のあるユーザー — パーキンソン病、振戦、細かい運動制御の制限がある人など — は、代替ポインティングデバイス、ヘッドポインタ、視線追跡システムを使用することがあります。これらのユーザーにとってポインタを正確に制御することは難しく、小さなトリガー要素から小さなツールチップへ、両方から外れずに移動することは、ホバー領域に余裕がなければほぼ不可能です。ホバー可能の要件はこれを直接的に解決します。

認知障害のあるユーザー は、読むのが遅かったり、内容を読み返す必要があったりします。数秒後に自動的に閉じてしまうツールチップは、これらのユーザーが情報を十分に理解する時間を与えません。また、フォーカスを移動しないと閉じられないツールチップは、混乱したインタラクション状態にユーザーの注意を閉じ込めてしまう可能性があります。

具体的なシナリオを考えてみましょう。ある銀行のウェブサイトが、口座の金利の詳細を、小さな情報アイコンにホバーしたときに表示されるツールチップ内に表示しているとします。400% にズームしたロービジョンのユーザーは、一度にページの一部しか見えません。アイコンにホバーするとツールチップが表示され、細かい注意書きを読むためにポインタをツールチップに向かって動かし始めますが、ツールチップは親要素のホバー状態にのみ紐づいているため、ポインタがトリガーから離れた瞬間に消えてしまいます。ユーザーは必要な開示情報にアクセスできません。これは単なるユーザビリティの不便さではなく、規制産業においては法的なアクセシビリティ障壁となり得ます。

障害に特化した影響を超えて、この達成基準を正しく実装することは、タッチとキーボードのハイブリッドデバイスを使うすべてのユーザーの一般的な使いやすさを向上させ、「消える」UI 要素が原因のサポート問い合わせを減らし、ユーザーや監査人に対してインターフェース品質の高さを示すことにもつながります。

関連する Axe-core のルール

WCAG 1.4.13 には手動テストが必要です。この達成基準は時間経過やポインタの動きに関わる挙動を含むため、自動ツールでは違反を確実に検出できません。静的な DOM 解析では評価できないからです。1.4.13 に直接対応する単一の axe-core ルールは存在しませんが、以下の観点は、なぜ自動化だけでは不十分なのか、また手動レビュー時に何を確認すべきかを説明します。

  • 手動テストが必要 — ホバー挙動: 自動スキャナは、ある時点の DOM と CSSOM を検査しますが、トリガー要素から新たにレンダリングされたツールチップに向かってポインタを動かし、その間ツールチップが持続するかどうかをシミュレートすることはできません。理論的には、CSS の :hover 疑似クラスが、親のホバーが外れたときに子要素を非表示にしていることを検出することはできますが、ポインタの経路をシミュレートしない限り、それが意図した閉じる挙動なのか、ホバー可能要件の不備なのかを区別できません。
  • 手動テストが必要 — Escape による閉じる操作: Escape キーを押したときにオーバーレイが閉じるかどうかを検出するには、axe-core の現在のルールセットを超える JavaScript イベントのシミュレーションが必要です。Axe はポップアップの ARIA ロール不足や aria-expanded 属性の欠如を指摘することはできますが、Escape の keydown リスナーが閉じる関数に紐づいていて、実際に要素を非表示にしているかどうかを検証することはできません。
  • 手動テストが必要 — 持続性 / 自動閉じる: 3秒後に setTimeout 呼び出しで自らを非表示にするツールチップは、その時間内に行われた静的スキャンでは完全に有効に見えます。オーバーレイを時間経過とともに観察するテスター、あるいは JavaScript ソースをレビューするテスターだけが、自動閉じるタイマーを違反として特定できます。
  • 手動チェックと併用すべき補完的な axe ルール: 1.4.13 を直接テストするわけではありませんが、aria-tooltip-name(ツールチップにアクセシブルネームがあることの確認)、color-contrast(ツールチップのテキストが判読可能であることの確認)、focus-visible(フォーカスされたトリガーが視覚的に識別できることの確認)といったルールを実行することで、1.4.13 の不備の影響を増幅させる関連問題を洗い出すことができます。

テスト方法

  1. 自動ベースラインスキャン: ホバー/フォーカスでトリガーされるコンテンツを含むページで axe DevTools や Lighthouse を実行します。ツールチップのロール、コントラスト、フォーカスの可視性に関してフラグされた問題を確認します。これらは 1.4.13 準拠を保証するものではありませんが、ベースラインを確立します。手動ステップで対象とするため、どの要素がオーバーレイコンテンツをトリガーするかを記録します。
  2. すべてのホバー/フォーカスでトリガーされるコンテンツの特定: ページをスクロールしながら、すべてのインタラクティブ要素に体系的にホバーします — アイコンボタン、追加説明のあるリンク、フォームフィールドのヒント、ナビゲーション項目、データテーブルのヘッダー、チャートのデータポイントなどです。追加コンテンツを表示させる要素をすべてリストアップします。
  3. ホバー可能要件のテスト: 特定した各トリガーについて、それにホバーしてオーバーレイを表示させ、次にトリガー要素からオーバーレイコンテンツ自体へポインタをゆっくり移動します。この移動の間、オーバーレイは表示されたままでなければなりません。ポインタがオーバーレイに到達する前に消えてしまう場合、その達成基準は不合格です。
  4. 閉じることができる要件のテスト: オーバーレイが表示されている間(ホバーまたはキーボードフォーカスによってトリガーされている場合)、Escape キーを押します。オーバーレイは閉じなければなりません。閉じない場合、その達成基準は不合格です。このテストは、ポインタがトリガー上にある状態と、ポインタがオーバーレイ上にある状態の両方で行います。
  5. 持続性要件のテスト: オーバーレイをトリガーし、ポインタをトリガーまたはオーバーレイの上に置いたまま、少なくとも 10〜15 秒待ちます。この間、オーバーレイは表示されたままでなければなりません。ユーザー操作なしにフェードアウトしたり、タイムアウトしたり、消えたりする場合、その達成基準は不合格です。
  6. キーボードのみのテスト: キーボードだけを使ってページを Tab で移動します。フォーカスが追加コンテンツを表示するトリガーに到達したとき、(a) コンテンツが表示されること、(b) Escape を押すとそれが閉じること、(c) フォーカスがトリガー上にある間はコンテンツが自動的に消えないことを確認します。NVDA と Firefox、JAWS と Chrome、VoiceOver と Safari を使用して、スクリーンリーダーがコンテンツを正しく提示していることも確認します。
  7. 画面拡大のテスト: ブラウザのズームを 400% に設定するか、OS レベルの拡大機能を有効にします。ホバーテストを繰り返します。ツールチップに到達するためにビューポートをパンしなければならないユーザーでも、ツールチップが消えることなくそれが可能であることを確認します。
  8. JavaScript ソースのレビュー: コードベース内で、オーバーレイの非表示ロジックに関連する setTimeoutmouseleavemouseoutblur のイベントハンドラを検索します。ポインタがオーバーレイ上にある間やトリガーがフォーカスを保持している間に非表示ロジックが発火しないこと、また自動閉じるタイマーが設定されていないことを確認します。

修正方法

mouseleave で消えてしまう CSS のみのツールチップ — 不適切な例

<!-- Tooltip only shown via CSS :hover on parent; disappears as soon as
     the pointer moves off the trigger toward the tooltip text -->
<span class='tip-wrapper'>
  Info
  <span class='tooltip'>This is the tooltip content.</span>
</span>

<!-- CSS (illustrative) -->
<!--
.tooltip { display: none; }
.tip-wrapper:hover .tooltip { display: block; }
-->

mouseleave で消えてしまう CSS のみのツールチップ — 適切な例

<!-- Correct: tooltip is also shown when the pointer is over the tooltip itself,
     and the gap between trigger and tooltip is covered so pointer movement
     does not accidentally dismiss the overlay. -->
<span class='tip-wrapper'>
  Info
  <span class='tooltip' role='tooltip' id='tip1'>This is the tooltip content.</span>
</span>

<!-- CSS (illustrative) -->
<!--
.tooltip { display: none; position: absolute; }
.tip-wrapper:hover .tooltip,
.tooltip:hover { display: block; }
/* Use padding or a transparent pseudo-element bridge between trigger and tooltip */
-->

Escape キーで閉じられない JavaScript ツールチップ — 不適切な例

<button aria-describedby='tip2' data-tooltip='Account balance details'>
  Balance
</button>
<div id='tip2' role='tooltip' hidden>Account balance details</div>

<script>
// Only mouseenter/mouseleave — no keyboard or Escape handling
document.querySelector('button').addEventListener('mouseenter', () => {
  document.getElementById('tip2').removeAttribute('hidden');
});
document.querySelector('button').addEventListener('mouseleave', () => {
  document.getElementById('tip2').setAttribute('hidden', '');
});
</script>

Escape キーで閉じられない JavaScript ツールチップ — 適切な例

<button aria-describedby='tip2' data-tooltip='Account balance details'>
  Balance
</button>
<div id='tip2' role='tooltip' hidden>Account balance details</div>

<script>
const btn = document.querySelector('button');
const tip = document.getElementById('tip2');

function showTip() { tip.removeAttribute('hidden'); }
function hideTip() { tip.setAttribute('hidden', ''); }

// Show on hover and focus
btn.addEventListener('mouseenter', showTip);
btn.addEventListener('focus', showTip);

// Hide only when pointer leaves BOTH trigger AND tooltip
btn.addEventListener('mouseleave', (e) => {
  // Short delay allows pointer to reach the tooltip
  setTimeout(() => {
    if (!tip.matches(':hover') && !btn.matches(':hover')) hideTip();
  }, 100);
});
tip.addEventListener('mouseleave', () => {
  if (!btn.matches(':hover')) hideTip();
});

// Hide on blur (keyboard)
btn.addEventListener('blur', hideTip);

// Dismissible via Escape key — required by 1.4.13
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape' && !tip.hidden) hideTip();
});
</script>

setTimeout による自動閉じるツールチップ — 不適切な例

<button id='info-btn'>More info</button>
<div id='tip3' role='tooltip' hidden>Here is the additional information for this field.</div>

<script>
document.getElementById('info-btn').addEventListener('mouseenter', () => {
  const t = document.getElementById('tip3');
  t.removeAttribute('hidden');
  // Violation: auto-dismisses after 3 seconds regardless of user state
  setTimeout(() => t.setAttribute('hidden', ''), 3000);
});
</script>

setTimeout による自動閉じるツールチップ — 適切な例

<button id='info-btn' aria-describedby='tip3'>More info</button>
<div id='tip3' role='tooltip' hidden>Here is the additional information for this field.</div>

<script>
const btn2 = document.getElementById('info-btn');
const tip3 = document.getElementById('tip3');

// No setTimeout — tooltip persists until user removes hover/focus or presses Escape
function show() { tip3.removeAttribute('hidden'); }
function hide() {
  setTimeout(() => {
    if (!tip3.matches(':hover') && !btn2.matches(':hover') && document.activeElement !== btn2) {
      tip3.setAttribute('hidden', '');
    }
  }, 100);
}

btn2.addEventListener('mouseenter', show);
btn2.addEventListener('focus', show);
btn2.addEventListener('mouseleave', hide);
btn2.addEventListener('blur', hide);
tip3.addEventListener('mouseleave', hide);
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') tip3.setAttribute('hidden', '');
});
</script>

よくある間違い

  • トリガーとツールチップの間の隙間を埋めずに CSS の :hover のみを使用すること: トリガー要素とツールチップコンテナの間に 1〜2 px でも隙間があると、その間をポインタが移動するときにホバー状態が解除され、ユーザーがツールチップに到達する前にそれが隠れてしまいます。この隙間を埋めるために、透明な疑似要素や重なり合うパディングを使用してください。
  • ポインタがツールチップに移動したかどうかを確認せずに、トリガーの mouseleave に非表示ロジックをバインドすること: カーソルがトリガーから離れた瞬間にツールチップが消えてしまい、その移動先がツールチップ自体であっても同様です。非表示にする前に必ず tip.matches(':hover') を確認するか、短いディレイを伴うデバウンスを使用してください。
  • mouseenter と mouseleave に対して focus と blur イベントを紐づけるのを忘れること: キーボードのみのユーザーがトリガーに Tab で移動しても、マウスイベントしか処理していない場合はツールチップが表示されず、関連情報がマウスなしでは完全にアクセス不能になります。
  • クリックで閉じられるから十分だと考え、Escape キーリスナーを追加しないこと: キーボードユーザーや画面拡大ユーザーは、オーバーレイから簡単に「クリックして離れる」ことができません。Escape は、この達成基準において期待され、かつ必須の閉じる手段です。
  • Escape リスナーを document ではなくトリガー要素のみに配置すること: ユーザーがフォーカスをツールチップや別の要素に移動した場合、トリガーにスコープされたリスナーは発火しません。Escape ハンドラは、オーバーレイが開いているときに常にキーイベントを受け取る document か共通の祖先要素に配置する必要があります。
  • 固定時間後にツールチップを自動的に閉じるために setTimeout を使用すること: ポインタがトリガーやツールチップ上にある間、またはトリガーがキーボードフォーカスを保持している間に発火するタイマーによる閉じる処理は、持続性要件に対する明確な違反です。ホバー/フォーカスでトリガーされるオーバーレイからは、すべての自動閉じるタイマーを削除してください。
  • カスタムスタイリングで title 属性を置き換える形でのみツールチップの表示を行うこと: ネイティブの title ツールチップを削除し、カスタム版に置き換える開発者は、1.4.13 の3つの要件すべてを自分たちで実装しなければなりません。ブラウザネイティブのツールチップに対する例外は、同じパターンを再現したカスタム JavaScript には適用されません。
  • 400% ズームでの画面拡大テストを行わないこと: 通常のズームではアクセシブルに見えるツールチップでも、高いズームレベルでは画面外にはみ出し、ユーザーがパンする必要が生じる場合があります。そして、パンする前にツールチップが閉じてしまうと、100% ズームでは合格したテストが、実際の利用状況では不合格になります。
  • ツールチップコンテナに pointer-events: none を適用すること: この CSS プロパティは、ポインタがツールチップ上にあると見なされることを完全に防ぎ、他のロジックに関係なくホバー可能要件を満たすことを不可能にします。ユーザーが操作する必要がある、あるいは表示を維持するためにホバーする必要があるツールチップには、決して pointer-events: none を適用してはいけません。
  • ARIA の role='tooltip' だけで準拠として扱うこと: role='tooltip'aria-describedby を追加することはスクリーンリーダーのアクセシビリティにとって重要ですが、別のレイヤーの問題を扱っているに過ぎません。これらの ARIA 属性は、コンテンツを自動的に閉じることができるようにしたり、ホバー可能にしたり、持続させたりするものではありません。インタラクションの挙動は、依然として明示的に実装する必要があります。

トルコのアクセシビリティ規制との関係

2025年6月21日付の官報(番号 32933)で公布されたトルコ大統領通達 2025/10 は、WCAG 基準を引用する形で正式なアクセシビリティ義務を定めています。この通達は、対象組織に対し、国際的に認められたガイドラインと整合したウェブアクセシビリティ対策の実施を求めており、レベル AA 準拠 — そこには WCAG 1.4.13 も含まれます — は強く推奨されるとともに、家族・社会サービス省(Aile ve Sosyal Hizmetler Bakanlığı)が発行するアクセシビリティロゴ(Erişilebilirlik Logosu)を取得しようとする組織にとっては必須となっています。

この通達は、トルコで活動する幅広い種類の組織を対象としています。あらゆる行政レベルの公共機関や政府機関は、デジタルサービスをアクセシブルにすることが求められます。民間セクターでは、その義務は、電子商取引プラットフォーム、銀行および金融サービス提供者、病院や民間医療機関、20万以上の加入者を持つ通信事業者、旅行代理店、民間輸送会社、国民教育省(Millî Eğitim Bakanlığı)の認可の下で運営される私立学校にまで及びます。

WCAG 1.4.13 は、ツールチップやポップオーバーパターンが広く使われているトルコのデジタルコンテキストにおいて特に重要です。たとえば e-Devlet 連携などの電子政府ポータル、手数料や金利情報をツールチップで表示する銀行・フィンテックのインターフェース、ホバーでトリガーされるオーバーレイを通じて追加の案内を提示する医療予約システムなどです。1.4.13 に不合格な銀行プラットフォームは、ツールチップで提供される金利開示をロービジョンの顧客が読めない状況を生み出す可能性があり、これはアクセシビリティと金融消費者保護の両面で問題となります。

Erişilebilirlik Logosu の取得を目指す組織に対して行われるアクセシビリティ監査では、自動ツールではこれらの違反を検出できないため、ホバーやフォーカスの挙動に関する手動テストが必ず含まれます。Accsible のようなアクセシビリティオーバーレイ SDK を使用している組織は、SDK 自体が挿入するウィジェット由来のツールチップ、ガイド付きツアーのポップオーバー、コンテキストヘルプパネルが、1.4.13 の3つの要件 — Escape で閉じられること、ホバーしても閉じないこと、ユーザー操作まで持続すること — を完全に満たしていることを確認しなければなりません。そうしなければ、アクセシビリティ向上を目的としたツール自体が新たな障壁を生み出し、規制遵守とユーザーの信頼の両方を損なうことになります。