本文へスキップ
JavaScript 応用 JavaScript アクセシビリティ フォーカス管理

アクセシブルなUIをJavaScriptで作ろう

更新日:2026-08-02

クリックで動くだけでは、キーボードや支援技術の利用者へ状態が伝わらない場合があります。HTMLの意味を土台に、必要な状態だけをJavaScriptで同期します。

アクセシビリティは一部の人向けの追加機能ではありません。キーボード利用、音声読み上げ、拡大表示、一時的なけがなど、異なる方法でも同じ情報と操作へ到達できるようにする考え方です。

今回使用するファイル構成

javascript-practice/
├── index.html
├── css/
│   └── style.css
└── js/
    └── script.js

開閉UIの状態を伝える

button要素を使う

操作にはdivではなくbuttonを使います。buttonはTabキーで移動でき、EnterキーやSpaceキーで操作できます。

<button id="details-button" type="button" aria-expanded="false" aria-controls="details-panel">
  詳細を開く
</button>
<div id="details-panel" hidden>詳細情報です。</div>

表示と属性を同期する

const button = document.querySelector('#details-button');
const panel = document.querySelector('#details-panel');

button.addEventListener('click', () => {
  const isOpen = button.getAttribute('aria-expanded') === 'true';

  panel.hidden = isOpen;
  button.setAttribute('aria-expanded', String(!isOpen));
  button.textContent = isOpen ? '詳細を開く' : '詳細を閉じる';
});

フォーカスを管理する

モーダルではフォーカスを管理する

モーダルを開いたら内部へフォーカスを移し、閉じたら開いたボタンへ戻します。

let opener;

function openDialog(dialog, button) {
  opener = button;
  dialog.showModal();
  dialog.querySelector('button')?.focus();
}

function closeDialog(dialog) {
  dialog.close();
  opener?.focus();
}

実装後はTab、Shift+Tab、Enter、Space、Escapeで確認します。

アコーディオンを完成させる

複数の開閉項目を用意する

<section class="accordion">
  <h2>
    <button class="accordion-button" type="button"
      aria-expanded="false" aria-controls="answer-1">
      JavaScriptとは何ですか
    </button>
  </h2>
  <div id="answer-1" hidden>
    <p>Webページへ動きを加えられるプログラミング言語です。</p>
  </div>

  <h2>
    <button class="accordion-button" type="button"
      aria-expanded="false" aria-controls="answer-2">
      事前知識は必要ですか
    </button>
  </h2>
  <div id="answer-2" hidden>
    <p>HTMLとCSSの基礎を学んでから進むことを推奨します。</p>
  </div>
</section>

aria-controlsには、操作対象のidを指定します。見出しの中へボタンを置くことで、質問が文書構造上の見出しであり、操作可能でもあることを表します。

表示状態を同期する

const accordionButtons = document.querySelectorAll('.accordion-button');

accordionButtons.forEach((button) => {
  button.addEventListener('click', () => {
    const panelId = button.getAttribute('aria-controls');
    const panel = document.querySelector(`#${panelId}`);
    const isExpanded = button.getAttribute('aria-expanded') === 'true';

    button.setAttribute('aria-expanded', String(!isExpanded));
    panel.hidden = isExpanded;
  });
});

状態はaria-expandedhiddenの両方で一致させます。見た目だけクラスで切り替え、支援技術へ閉じた状態を伝え忘れないようにします。

dialog要素でモーダルを作る

<button id="open-dialog" type="button">利用規約を開く</button>

<dialog id="terms-dialog" aria-labelledby="dialog-title">
  <h2 id="dialog-title">利用規約</h2>
  <p>利用規約の内容です。</p>
  <button id="close-dialog" type="button">閉じる</button>
</dialog>
const openButton = document.querySelector('#open-dialog');
const closeButton = document.querySelector('#close-dialog');
const dialog = document.querySelector('#terms-dialog');

openButton.addEventListener('click', () => {
  dialog.showModal();
  closeButton.focus();
});

closeButton.addEventListener('click', () => {
  dialog.close();
});

dialog.addEventListener('close', () => {
  openButton.focus();
});

showModal()を使うと、モーダル外をキーボードで操作できない状態やEscapeキーによる閉じる動作をブラウザが支援します。独自のdivだけで同じ機能を再現するより、意味のある標準要素を優先します。

キーボードと画面表示を検証する

  1. マウスを使わずTabキーで全ボタンへ移動する
  2. EnterキーとSpaceキーでアコーディオンを操作する
  3. モーダルを開いた直後のフォーカス位置を確認する
  4. Escapeキーで閉じ、開くボタンへ戻ることを確認する
  5. 200%まで拡大して内容やボタンが欠けないか確認する

可能であれば、WindowsのNarrator、macOSのVoiceOver、NVDAなどでも状態の読み上げを確認します。

避けたい実装

  • クリック可能なdivへ見た目だけボタンを再現する
  • フォーカスの輪郭を代替表示なしで消す
  • 色の違いだけで状態を伝える
  • 自動的にフォーカスを頻繁に移動する
  • aria-*だけを付け、実際のキーボード操作を実装しない

ARIAはHTMLの意味を補うもので、標準要素の機能を置き換えるものではありません。

まとめ

意味のあるHTML、状態属性、フォーカスを同期させます。次の記事では、これまでの知識をTodoアプリへまとめます。