実践Webアクセシビリティ

アクセシブルなツールチップとポップオーバーの実装:キーボード操作とARIA活用

Tags: アクセシビリティ, UIコンポーネント, ツールチップ, ポップオーバー, ARIA, キーボード操作

はじめに

ウェブサイトやアプリケーションでよく利用されるUIコンポーネントの一つに、ツールチップやポップオーバーがあります。要素にマウスカーソルを重ねたり、クリックしたりした際に、追加情報が表示される仕組みです。しかし、これらがマウス操作にのみ依存している場合、キーボード操作やスクリーンリーダーを利用するユーザーにとっては情報にアクセスできなかったり、使いづらさを感じたりする大きな要因となります。

本記事では、ツールチップとポップオーバーをアクセシブルに実装するための具体的な方法を、コード例を交えながら解説します。

なぜツールチップ・ポップオーバーのアクセシビリティ対応が必要か

ツールチップやポップオーバーは、通常、要素の :hover:focus といった状態、あるいはクリックイベントによって表示・非表示が切り替わります。

これらの課題を解決し、すべてのユーザーがツールチップやポップオーバーによって提供される情報にアクセスできるようにすることが、アクセシブルな実装の目的です。

アクセシブルな実装方法の基本原則

アクセシブルなツールチップやポップオーバーを実装するためには、以下の点を考慮する必要があります。

  1. キーボード操作への対応:

    • トリガー要素(ツールチップを表示させる要素)にフォーカスが当たった際にツールチップ/ポップオーバーが表示されるようにする。
    • 表示されたツールチップ/ポップオーバーは、Escキーで閉じられるようにする。
    • ポップオーバーの場合、内部にインタラクティブな要素(リンクやボタンなど)が含まれる可能性があるため、表示されたポップオーバー内部にフォーカスが移動し、タブ移動できる必要がある場合があります。(ツールチップは通常、補足情報のみのため、フォーカス移動は不要なケースが多いです)
    • ポップオーバーが閉じられた後、フォーカスが適切な位置(通常はトリガー要素)に戻るように制御する。
  2. ARIA属性の活用:

    • トリガー要素とツールチップ/ポップオーバーの内容との関連付けを明確にするために、ARIA属性を使用します。
    • ツールチップ: aria-describedby 属性が適しています。トリガー要素に aria-describedby="[ツールチップ要素のID]" を指定することで、スクリーンリーダーはトリガー要素にフォーカスが当たった際にそのラベルや役割に加えて、関連付けられたツールチップの内容を読み上げます。
    • ポップオーバー: aria-haspopup 属性を使用して、トリガー要素がポップアップ要素(メニュー、ダイアログなど)を制御することを示します。また、表示状態を aria-expanded 属性で示し、トリガー要素に aria-controls="[ポップオーバー要素のID]" を使用して制御する要素との関連を示すことも検討できます。ポップオーバーがダイアログのような役割を持つ場合は role="dialog"aria-modal="true" なども使用します。
  3. 表示・非表示の制御:

    • CSSの :hover:focus だけでは、キーボード操作による表示や、JavaScriptでの動的な制御、ARIA属性の切り替えが困難です。JavaScriptを使って、トリガー要素へのフォーカスイベント、クリックイベント、Escキーの押下などを検知し、ツールチップ/ポップオーバー要素の表示・非表示を切り替えるのが一般的です。
    • 表示・非表示の切り替えには、要素の display プロパティを noneblock/inline-block などで切り替えるか、visibility プロパティを hiddenvisible で切り替える方法があります。display: none;visibility: hidden; は、要素がアクセシビリティツリーから削除されるため、非表示状態ではスクリーンリーダーに読み上げられません。コンテンツを存在させたまま非表示にする場合は、opacity: 0; かつ pointer-events: none; といったCSSで視覚的に隠す方法もありますが、ツールチップ/ポップオーバーの場合は、必要な時だけ存在を知らせたいケースが多いため、displayvisibility による制御が適していることが多いです。また、hidden 属性([hidden])の利用も有効です。

具体的な実装例(ツールチップ)

シンプルなツールチップの実装例を示します。トリガー要素はボタンとし、フォーカス時とホバー時にツールチップが表示され、Escキーで閉じられるようにします。

HTML

ツールチップの内容を持つ要素に一意のIDを付与し、トリガー要素に aria-describedby でそのIDを指定します。ツールチップ要素は、初期状態で hidden 属性またはCSSで非表示にしておきます。

<button id="tooltip-trigger" aria-describedby="tooltip-content">
  設定
</button>

<span id="tooltip-content" role="tooltip" class="tooltip">
  設定画面を開くためのボタンです。
</span>

ポイント: * ツールチップ要素には role="tooltip" を付与します。 * 初期状態ではCSSで非表示にしますが、hidden 属性を使うのも良い方法です。

CSS

ツールチップ要素を初期状態で非表示にし、トリガー要素のホバーまたはフォーカス時に表示するようにします。位置調整のCSSは適宜調整してください。

/* ツールチップを初期状態で非表示 */
.tooltip {
  visibility: hidden; /* または display: none; */
  opacity: 0;
  transition: opacity 0.2s ease-in-out; /* フェードイン/アウト */
  position: absolute;
  /* 位置調整のためのスタイルは省略 */
}

/* トリガー要素のホバーまたはフォーカス時に表示 */
#tooltip-trigger:hover + .tooltip,
#tooltip-trigger:focus + .tooltip,
#tooltip-trigger[aria-describedby]:not([hidden]) + .tooltip /* JavaScriptでhidden属性を制御する場合 */
{
  visibility: visible;
  opacity: 1;
}

/* JavaScriptでaria-expandedやhidden属性を切り替える場合は、
   その属性の状態に応じて表示・非表示を制御するCSSも記述 */
#tooltip-trigger[aria-expanded="true"] + .tooltip,
.tooltip:not([hidden]) { /* hidden属性が付いていない場合に表示 */
    visibility: visible;
    opacity: 1;
}

/* JavaScriptで display: none; を切り替える場合は、
   .tooltip.is-active { display: block; } のようなクラスを使う */

ポイント: * :hover:focus の両方で表示されるようにします。 * CSSセレクターで隣接セレクター(+)を使用していますが、HTML構造によっては親要素に対するホバー/フォーカスを利用したり、JavaScriptでクラスを付与したりする方法がより柔軟です。後述のJavaScript制御を推奨します。

JavaScript

キーボード操作(特にEscキー)での非表示や、より複雑な表示制御を行うためにJavaScriptを使用します。

const trigger = document.getElementById('tooltip-trigger');
const tooltip = document.getElementById('tooltip-content');

if (trigger && tooltip) {
  // 初期状態ではツールチップを非表示 (HTML/CSSでも設定)
  tooltip.setAttribute('hidden', ''); // hidden属性を使用

  // フォーカスまたはマウスオーバーで表示
  trigger.addEventListener('focus', showTooltip);
  trigger.addEventListener('mouseenter', showTooltip);

  // フォーカスアウトまたはマウスリーブで非表示
  trigger.addEventListener('blur', hideTooltip);
  trigger.addEventListener('mouseleave', hideTooltip);

  // Escキーで非表示
  document.addEventListener('keydown', (event) => {
    if (event.key === 'Escape') {
      hideTooltip();
    }
  });

  function showTooltip() {
    tooltip.removeAttribute('hidden'); // hidden属性を削除して表示
    // aria-expanded などの状態を示す属性が必要ならここで設定
    // trigger.setAttribute('aria-expanded', 'true'); // ポップオーバーの場合は検討
  }

  function hideTooltip() {
    // ツールチップがフォーカスを受け取ることがない場合はこれで十分
    tooltip.setAttribute('hidden', ''); // hidden属性を設定して非表示
    // trigger.setAttribute('aria-expanded', 'false'); // ポップオーバーの場合は検討
  }
}

ポイント: * focus/blur イベントでキーボード操作に対応します。 * mouseenter/mouseleave イベントでマウス操作に対応します。 * hidden 属性を使うと、CSSとJavaScriptで表示状態を一元管理しやすくなります。 * document 全体でEscキーを監視し、表示中のツールチップ/ポップオーバーを閉じるようにします。

具体的な実装例(ポップオーバー)

ポップオーバーはツールチップよりも複雑になることがあります。インタラクティブな要素が含まれる場合や、モーダルのような振る舞いが求められる場合があるためです。ここでは、クリックで表示・非表示が切り替わり、Escキーで閉じられるシンプルな例を示します。

HTML

ポップオーバー要素に一意のIDを付与し、トリガー要素に aria-haspopuparia-controls を指定します。

<button id="popover-trigger" aria-haspopup="dialog" aria-expanded="false" aria-controls="popover-content">
  詳細を表示
</button>

<div id="popover-content" role="dialog" aria-labelledby="popover-title" hidden>
  <h3 id="popover-title">追加情報のタイトル</h3>
  <p>ここに詳細情報が入ります。</p>
  <button id="popover-close">閉じる</button>
</div>

ポイント: * トリガー要素に aria-haspopup="dialog" を指定し、クリックでダイアログのようなポップアップが表示されることを示します。(表示される内容に応じて menu, listbox など適切な値を設定します) * aria-expanded="false" で初期状態が閉じていることを示し、開閉時にJavaScriptで true/false を切り替えます。 * aria-controls="popover-content" で、このボタンが popover-content というIDの要素を制御することを示します。 * ポップオーバー要素に role="dialog" を指定し、ダイアログであることを明示します。 * ポップオーバーのタイトル要素にIDを付与し、ポップオーバー要素に aria-labelledby でそのIDを指定することで、スクリーンリーダーがポップオーバーを開いた際にタイトルを読み上げるようにします。 * 初期状態は hidden 属性で非表示にします。

CSS

ツールチップと同様に、hidden 属性がない場合に表示されるCSSを記述します。

.popover {
  /* 位置調整など */
  position: absolute;
  border: 1px solid #ccc;
  padding: 1em;
  background: white;
  box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

.popover[hidden] {
  display: none; /* hidden属性があれば非表示 */
}

/* JavaScriptでクラスを切り替える場合は .popover.is-open { ... } のように */

JavaScript

クリックイベントでの表示・非表示切り替え、Escキーでの非表示、そしてフォーカス管理が重要になります。

const trigger = document.getElementById('popover-trigger');
const popover = document.getElementById('popover-content');
const closeButton = document.getElementById('popover-close');

if (trigger && popover && closeButton) {
  // クリックで開閉
  trigger.addEventListener('click', togglePopover);
  closeButton.addEventListener('click', closePopover);

  // Escキーで閉じる
  document.addEventListener('keydown', (event) => {
    if (event.key === 'Escape') {
      closePopover();
    }
  });

  function togglePopover() {
    const isExpanded = trigger.getAttribute('aria-expanded') === 'true';
    if (isExpanded) {
      closePopover();
    } else {
      openPopover();
    }
  }

  function openPopover() {
    // aria属性の更新
    trigger.setAttribute('aria-expanded', 'true');
    // hidden属性を削除して表示
    popover.removeAttribute('hidden');

    // フォーカス管理: ポップオーバー内の最初のインタラクティブ要素、
    // またはポップオーバー自体にフォーカスを移動
    // ここでは閉じるボタンにフォーカスを移動する例
    closeButton.focus();
  }

  function closePopover() {
    // aria属性の更新
    trigger.setAttribute('aria-expanded', 'false');
    // hidden属性を設定して非表示
    popover.setAttribute('hidden', '');

    // フォーカス管理: トリガー要素にフォーカスを戻す
    trigger.focus();
  }

  // ポップオーバーの外側をクリックで閉じる(オプション)
  document.addEventListener('click', (event) => {
    const isClickInside = trigger.contains(event.target) || popover.contains(event.target);
    if (!isClickInside && popover.hasAttribute('hidden') === false) {
      closePopover();
    }
  });
}

ポイント: * クリックイベントで aria-expandedhidden 属性を切り替えます。 * openPopover 関数内で、表示後にポップオーバー内の要素(ここでは閉じるボタン)にフォーカスを移動させます。これにより、キーボードユーザーが表示された内容にすぐにアクセスできます。 * closePopover 関数内で、非表示後にトリガー要素にフォーカスを戻します。これにより、ユーザーは操作を継続しやすくなります。 * Escキーでの非表示に対応します。 * ポップオーバーの外側をクリックで閉じる機能を追加する場合は、上記のようにイベントリスナーを設定します。ただし、この処理はフォーカス管理やモーダルではないポップアップ(非モーダル)の挙動に影響する場合があるため、慎重に実装してください。

実装時の注意点

テスト方法

実装したアクセシビリティ対応が正しく機能しているか、以下の方法でテストします。

まとめ

ツールチップやポップオーバーのアクセシブルな実装は、キーボードユーザーやスクリーンリーダーユーザーが情報にアクセスするために不可欠です。本記事で紹介したように、HTMLでの適切なマークアップ、ARIA属性の活用、そしてJavaScriptによるキーボード操作と表示状態の制御を組み合わせることで、多くのユーザーにとって使いやすいコンポーネントを提供できます。

単にマウスホバーやクリックで表示するだけでなく、フォーカス時の表示、Escキーでの非表示、そして適切なARIA属性の付与を忘れずに行い、提供する情報が誰にでも利用可能であることを目指しましょう。テストを繰り返し行い、より多くのユーザーが快適に情報にアクセスできるウェブサイトを構築していくことが重要です。