実践Webアクセシビリティ

アクセシブルなタブUIの実装:キーボード操作とARIA属性の活用

Tags: Webアクセシビリティ, UIコンポーネント, HTML, ARIA, JavaScript, キーボード操作

ウェブサイトやアプリケーションでよく利用されるタブUIは、コンテンツを効率的に整理し、ユーザーに選択肢を提供するための効果的な手段です。しかし、適切に実装されないと、キーボード操作ができない、スクリーンリーダーのユーザーが情報を理解できないなど、アクセシビリティ上の大きな課題を生じさせることがあります。

この記事では、アクセシブルなタブUIを実装するための具体的な手法として、キーボード操作への対応とARIA属性の活用に焦点を当て、コード例を交えて解説します。

タブUIにおけるアクセシビリティ課題

一般的なタブUIの実装では、マウスによるクリック操作のみを想定している場合があります。これにより、以下のような問題が発生します。

なぜキーボード操作とARIA属性が必要か

これらの課題を解決し、より多くのユーザーがタブUIを快適に利用できるようにするために、キーボード操作への対応とARIA属性の活用は不可欠です。

具体的な実装手順

ここでは、HTML、CSS、JavaScriptを組み合わせてアクセシブルなタブUIを実装する手順を解説します。

1. 基本となるHTML構造

タブUIの標準的なHTML構造は、WCAGやARIAのベストプラクティスに基づいています。タブのリストと、それに対応するタブパネルのリストで構成するのが一般的です。

<div class="tab-container">
  <div role="tablist" aria-label="コンテンツのカテゴリ">
    <button role="tab" id="tab-1" aria-selected="true" aria-controls="panel-1">タブ1</button>
    <button role="tab" id="tab-2" aria-selected="false" aria-controls="panel-2" tabindex="-1">タブ2</button>
    <button role="tab" id="tab-3" aria-selected="false" aria-controls="panel-3" tabindex="-1">タブ3</button>
  </div>
  <div id="panel-1" role="tabpanel" aria-labelledby="tab-1">
    <h3>タブ1の内容</h3>
    <p>これはタブ1に対応するコンテンツです。</p>
  </div>
  <div id="panel-2" role="tabpanel" aria-labelledby="tab-2" hidden>
    <h3>タブ2の内容</h3>
    <p>これはタブ2に対応するコンテンツです。</p>
  </div>
  <div id="panel-3" role="tabpanel" aria-tabpanel="tab-3" hidden>
    <h3>タブ3の内容</h3>
    <p>これはタブ3に対応するコンテンツです。</p>
  </div>
</div>

ポイント:

2. CSSによるスタイリングと非表示制御

非アクティブなタブパネルを隠すためにCSSを使用します。hidden属性を利用するのがセマンティックですが、display: none;でも問題ありません。

.tab-container [role="tabpanel"][hidden] {
  display: none; /* または visibility: hidden; opacity: 0; など、要件に応じて */
}

/* アクティブなタブの視覚的なスタイル */
.tab-container [role="tab"][aria-selected="true"] {
  font-weight: bold; /* 例 */
  border-bottom: 2px solid blue; /* 例 */
}

ポイント:

3. JavaScriptによる操作と状態管理

JavaScriptを使用して、ユーザーの操作(クリック、キーボード)に応じてタブの切り替えと状態(aria-selectedhiddentabindex)の更新を行います。

document.addEventListener('DOMContentLoaded', () => {
  const tabs = document.querySelectorAll('[role="tab"]');
  const tabPanels = document.querySelectorAll('[role="tabpanel"]');
  const tabList = document.querySelector('[role="tablist"]');

  // タブのアクティブ状態を切り替える関数
  const activateTab = (targetTab) => {
    // 全てのタブとパネルを非アクティブ状態にする
    tabs.forEach(tab => {
      tab.setAttribute('aria-selected', 'false');
      tab.setAttribute('tabindex', '-1'); // 非選択タブはTab移動から除外
    });
    tabPanels.forEach(panel => {
      panel.setAttribute('hidden', '');
    });

    // 選択されたタブと対応するパネルをアクティブ状態にする
    targetTab.setAttribute('aria-selected', 'true');
    targetTab.setAttribute('tabindex', '0'); // 選択タブはTab移動の対象にする
    const targetPanelId = targetTab.getAttribute('aria-controls');
    const targetPanel = document.getElementById(targetPanelId);
    if (targetPanel) {
      targetPanel.removeAttribute('hidden');
    }

    // フォーカスを切り替えたタブに移動(キーボード操作時)
    targetTab.focus();
  };

  // クリックイベントリスナー
  tabList.addEventListener('click', (event) => {
    const targetTab = event.target.closest('[role="tab"]');
    if (targetTab) {
      activateTab(targetTab);
    }
  });

  // キーボードイベントリスナー
  tabList.addEventListener('keydown', (event) => {
    const currentTab = document.activeElement.closest('[role="tab"]');
    if (!currentTab) return;

    let nextTab;

    switch (event.key) {
      case 'ArrowRight':
      case 'ArrowLeft':
        // 右矢印キーまたは左矢印キーでタブ間を移動
        event.preventDefault(); // デフォルトのスクロールなどを防ぐ
        const direction = (event.key === 'ArrowRight') ? 1 : -1;
        const currentIndex = Array.from(tabs).indexOf(currentTab);
        let nextIndex = (currentIndex + direction + tabs.length) % tabs.length;
        nextTab = tabs[nextIndex];
        break;
      case 'Home':
        // Homeキーで最初のタブへ移動
        event.preventDefault();
        nextTab = tabs[0];
        break;
      case 'End':
        // Endキーで最後のタブへ移動
        event.preventDefault();
        nextTab = tabs[tabs.length - 1];
        break;
      case 'Enter':
      case ' ': // Spaceキー
        // EnterまたはSpaceキーでタブを選択(activateTab関数内でフォーカス移動も行う)
        // クリックイベントと同じ処理なのでここでは特に何もしない
        // activateTab(currentTab); は不要、既にactivateTabが呼ばれるため
        break;
      default:
        return; // 他のキーは処理しない
    }

    if (nextTab && (event.key === 'ArrowRight' || event.key === 'ArrowLeft' || event.key === 'Home' || event.key === 'End')) {
       activateTab(nextTab); // キーボード操作でタブを切り替え
    }
  });
});

ポイント:

実装時の注意点やよくある落とし穴

テスト方法

実装したタブUIがアクセシブルであるかを確認するためには、以下の方法でテストを実施します。

まとめ

アクセシブルなタブUIを実装するには、単に見た目を整えるだけでなく、キーボード操作への対応とARIA属性の適切な利用が不可欠です。HTMLで適切なセマンティクスを定義し、JavaScriptでキーボードナビゲーションと状態管理を実装し、ARIA属性でその役割や状態を支援技術に正確に伝えることで、より多くのユーザーが快適に利用できるタブUIを実現できます。

この記事で紹介したコード例や手順を参考に、ご自身のプロジェクトでアクセシブルなタブUIの実装に取り組んでいただければ幸いです。実装後は必ず様々な方法でテストを行い、アクセシビリティが確保されていることを確認してください。