0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

セマンティックコーディングでアクセシビリティを向上させる実践ガイド

Last updated at Posted at 2025-08-21

Web開発において、セマンティックコーディングとアクセシビリティは重要な要素です。この記事では、role属性とaria属性を中心に、実際のコード例とベストプラクティスを交えて解説していきます!

TL;DR → セマンティックHTMLを基本とし、必要に応じてrole属性とaria属性を組み合わせることで、アクセシブルで保守性の高いコードが書ける

目次

  1. セマンティックコーディングとは
  2. role属性の基礎と実践
  3. aria属性の基礎と実践
  4. 実践的な例
  5. まとめ

セマンティックコーディングとは

定義

セマンティックコーディングとは、HTMLの要素に意味を持たせ、コンテンツの構造と目的を明確にすることです。

メリット

エンジニアや閲覧者だけでなく、機械にとっても構造がわかりやすいページを作るということなので、以下の良いことがあります!

  • スクリーンリーダー、リッチリザルト、音声検索、AI要約などで扱いやすくなる
  • SEOの向上
  • コードの可読性向上
  • メンテナンス性の向上
  • etc

悪い例 vs 良い例

<!-- ❌ 悪い例 -->
<div class="header">
<div class="nav">
<div class="main">
<div class="footer">

<!-- ✅ 良い例 -->
<header>
<nav>
<main>
<footer>

セマンティックHTMLを使うことで、ブラウザやスクリーンリーダーが要素の役割を理解しやすくなります!

role属性の基礎と実践

role属性とは

role属性は、HTML要素に明示的な役割を定義する属性です。スクリーンリーダーが要素の役割を理解するのに役立ちます。

ただし、重要な注意点があります:

  • role属性は要素の意味的な役割を定義するだけで、機能は追加しません
  • role="button"を付けても、その要素が自動的にフォーカス可能になったり、キーボード操作が可能になるわけではありません
  • 完全なアクセシビリティを実現するには、JavaScriptでの実装も必要です

よく使うrole属性

role属性 説明 使用例
button ボタンとして扱う <div role="button">
navigation ナビゲーション <div role="navigation">
main メインコンテンツ <div role="main">
complementary 補足情報 <div role="complementary">
banner ヘッダー <div role="banner">

role属性の実際の動作を確認してみよう

以下の2つのボタンを比較してみましょう:

<!-- 通常のbutton要素 -->
<button>通常のボタン</button>

<!-- div要素にrole="button"を追加 -->
<div role="button">role属性付きボタン</div>

<!-- div要素(role属性なし) -->
<div>role属性なしのdiv</div>

スクリーンショット 2025-08-21 17.06.07.png
通常のbutton要素:button role、focusableが自動的に設定される

スクリーンショット 2025-08-21 17.07.38.png
role="button"付きdiv要素:roleは設定されるが、focusableはfalseのまま

スクリーンショット 2025-08-21 17.23.06.png
role属性なしdiv要素:role="generic"(デフォルト)、focusable=""

重要な発見

  • <button>要素: role="button"focusable="true"が自動的に設定される
  • <div role="button">: role="button"は設定されるが、focusable="false"のまま
  • <div>要素(role属性なし): rolefocusableも設定されない(genericはdivのデフォルトのrole)
  • role属性だけでは不十分: 完全なアクセシビリティには追加の実装が必要(この場合は、tabindex="0"を付与してフォーカス可能にするべき)

セマンティックHTML vs role属性

セマンティックHTMLの例

<header>
    <h1>ヘッダー</h1>
    <nav>
        <a href="#">ホーム</a>
        <a href="#">サービス</a>
        <a href="#">お問い合わせ</a>
    </nav>
</header>
<main>
    <h2>メインコンテンツ</h2>
    <p>ここにメインの内容が入ります。</p>
</main>
<aside>
    <h3>サイドバー</h3>
    <p>補足情報</p>
</aside>
<footer>
    <p>&copy; 2024 セマンティックコーディング</p>
</footer>

role属性を使用した例

<div role="banner">
    <h1>ヘッダー (role="banner")</h1>
    <div role="navigation">
        <a href="#">ホーム</a>
        <a href="#">サービス</a>
        <a href="#">お問い合わせ</a>
    </div>
</div>
<div role="main">
    <h2>メインコンテンツ (role="main")</h2>
    <p>ここにメインの内容が入ります。</p>
</div>
<div role="complementary">
    <h3>サイドバー (role="complementary")</h3>
    <p>補足情報</p>
</div>
<div role="contentinfo">
    <p>&copy; 2024 セマンティックコーディング</p>
</div>

✅ セマンティックHTML

メリット

  • ブラウザ・スクリーンリーダーに標準で意味が伝わる
  • SEOに有利(検索エンジンが意味を理解しやすい)
  • コードの可読性・保守性が高い
  • パフォーマンスが良い(追加属性不要)
  • アクセシビリティ機能が自動で提供される(例: <button> なら自動的に Enter/Space キー対応)

デメリット

  • 意味のあるタグが用意されていないケースには対応できない
  • カスタムUIを作り込みたいときに標準挙動を上書きするのが大変

✅ role属性

メリット

  • セマンティックHTMLにない複雑UI(タブ、アコーディオン、モーダル等)も定義できる
  • 古いコードや非セマンティック要素を「後付け」で意味づけできる
  • 後方互換性や柔軟なカスタマイズに強い

デメリット

  • 機能は手動で実装する必要がある(キーボード操作や状態管理は自分で作らないとダメ)
  • 実装が複雑になりがち
  • 間違って付けると逆にアクセシビリティを壊す(誤用リスク)
  • メンテナンスが大変

aria属性の基礎と実践

aria属性とは

aria属性は、Accessible Rich Internet Applicationsの略で、Webアプリケーションのアクセシビリティを向上させる属性群です。
これを使用することで、標準であるタグやrole属性だけでは表すことができないものを表すことができます!

よく使うaria属性

aria属性 説明 使用例
aria-label 要素の名前(ラベル)を上書きする aria-label="メニューを開く"
aria-describedby 補足説明の参照(IDを指定) aria-describedby="help-text"
aria-hidden アクセシビリティツリーから除外する aria-hidden="true"
aria-expanded 展開状態(トグル制御側に付与) aria-expanded="false"
aria-pressed 押下状態(トグルボタン用) aria-pressed="false"
aria-controls 制御対象の要素IDを関連付ける aria-controls="panel-1"

実践的な例

アコーディオン (Accordion)

✅ セマンティック版(<details>/<summary>

<section aria-label="よくある質問">
  <details>
    <summary>質問1:配送はどのくらいかかりますか?</summary>
    <p>通常2〜3営業日でお届けします。</p>
  </details>
  <details>
    <summary>質問2:返品は可能ですか?</summary>
    <p>到着から7日以内であれば返品可能です。</p>
  </details>
</section>

✅ カスタムUI版(<button>+ARIA)

<section class="accordion" aria-label="よくある質問">
  <h3>
    <button class="acc-trigger" aria-expanded="false" aria-controls="acc-p1" id="acc-h1" type="button">
      質問1:配送はどのくらいかかりますか?
    </button>
  </h3>
  <div id="acc-p1" class="acc-panel" role="region" aria-labelledby="acc-h1" hidden>
    <p>通常2〜3営業日でお届けします。</p>
  </div>

  <h3>
    <button class="acc-trigger" aria-expanded="false" aria-controls="acc-p2" id="acc-h2" type="button">
      質問2:返品は可能ですか?
    </button>
  </h3>
  <div id="acc-p2" class="acc-panel" role="region" aria-labelledby="acc-h2" hidden>
    <p>到着から7日以内であれば返品可能です。</p>
  </div>
</section>

<script>
  const triggers = document.querySelectorAll('.acc-trigger');
  triggers.forEach(btn => {
    btn.addEventListener('click', () => {
      const expanded = btn.getAttribute('aria-expanded') === 'true';
      btn.setAttribute('aria-expanded', String(!expanded));
      document.getElementById(btn.getAttribute('aria-controls'))
        .toggleAttribute('hidden', expanded);
    });
    btn.addEventListener('keydown', e => {
      if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); btn.click(); }
    });
  });
</script>

タブ (Tabs)

※ タブ用のセマンティック要素はないため、ARIAパターンのみ書いてます。

✅ カスタムUI版(Tabs ARIA)

<div class="tabs" role="tablist" aria-label="商品情報">
  <button role="tab" id="tab-1" aria-selected="true" aria-controls="panel-1" tabindex="0">概要</button>
  <button role="tab" id="tab-2" aria-selected="false" aria-controls="panel-2" tabindex="-1">仕様</button>
  <button role="tab" id="tab-3" aria-selected="false" aria-controls="panel-3" tabindex="-1">レビュー</button>
</div>

<section id="panel-1" role="tabpanel" tabindex="0" aria-labelledby="tab-1">概要の内容…</section>
<section id="panel-2" role="tabpanel" tabindex="0" aria-labelledby="tab-2" hidden>仕様の内容…</section>
<section id="panel-3" role="tabpanel" tabindex="0" aria-labelledby="tab-3" hidden>レビューの内容…</section>

<script>
  const tabs = document.querySelectorAll('[role="tab"]');
  const panels = document.querySelectorAll('[role="tabpanel"]');

  function activate(tab) {
    tabs.forEach(t => { t.setAttribute('aria-selected', 'false'); t.tabIndex = -1; });
    panels.forEach(p => p.hidden = true);

    tab.setAttribute('aria-selected', 'true');
    tab.tabIndex = 0;
    const panel = document.getElementById(tab.getAttribute('aria-controls'));
    panel.hidden = false;
    tab.focus();
  }

  tabs.forEach(t => {
    t.addEventListener('click', () => activate(t));
    t.addEventListener('keydown', e => {
      const i = Array.from(tabs).indexOf(t);
      if (e.key === 'ArrowRight') activate(tabs[(i+1)%tabs.length]);
      if (e.key === 'ArrowLeft')  activate(tabs[(i-1+tabs.length)%tabs.length]);
      if (e.key === 'Home')       activate(tabs[0]);
      if (e.key === 'End')        activate(tabs[tabs.length-1]);
    });
  });
</script>

ポイント: role="tablist" / role="tab" / role="tabpanel"aria-selectedaria-controls、非表示は hidden、キーボード(左右・Home/End)。

モーダル (Modal / Dialog)

✅ セマンティック版(<dialog>

<button id="openDlg">モーダルを開く</button>

<dialog id="dlg" aria-labelledby="dlg-title" aria-describedby="dlg-desc">
  <h2 id="dlg-title">お知らせ</h2>
  <p id="dlg-desc">新しい機能が追加されました。</p>
  <form method="dialog">
    <button>閉じる</button>
  </form>
</dialog>

<script>
  const dlg = document.getElementById('dlg');
  const trigger = document.getElementById('openDlg');
  trigger.addEventListener('click', () => dlg.showModal());
  dlg.addEventListener('close', () => trigger.focus());
</script>

注意: <dialog> 要素は比較的新しく、サポート状況に差があります(IEは非対応、Safariも最近まで不完全)。そのため、本番環境ではカスタムUI版の使用を検討してください。

✅ カスタムUI版(role="dialog"aria-modal

<button id="openModal" aria-haspopup="dialog" aria-controls="modal">モーダルを開く</button>

<div id="modal" role="dialog" aria-modal="true"
     aria-labelledby="modal-title" aria-describedby="modal-desc" hidden>
  <div class="modal-surface">
    <h2 id="modal-title">お知らせ</h2>
    <p id="modal-desc">新しい機能が追加されました。</p>
    <button id="closeModal" aria-label="モーダルを閉じる">閉じる</button>
  </div>
</div>

<script>
  const modal = document.getElementById('modal');
  const openBtn = document.getElementById('openModal');
  const closeBtn = document.getElementById('closeModal');

  const focusable = () => modal.querySelectorAll('a,button,input,select,textarea,[tabindex]:not([tabindex="-1"])');

  function openModal() {
    modal.hidden = false;
    const first = focusable()[0];
    (first || closeBtn).focus();
    document.addEventListener('keydown', trap);
  }
  function closeModal() {
    modal.hidden = true;
    document.removeEventListener('keydown', trap);
    openBtn.focus();
  }
  function trap(e) {
    if (e.key === 'Escape') { e.preventDefault(); closeModal(); return; }
    if (e.key !== 'Tab') return;
    const list = Array.from(focusable());
    if (!list.length) return;
    const i = list.indexOf(document.activeElement);
    const next = e.shiftKey ? (i <= 0 ? list[list.length-1] : list[i-1]) : (i === list.length-1 ? list[0] : list[i+1]);
    e.preventDefault(); next.focus();
  }

  openBtn.addEventListener('click', openModal);
  closeBtn.addEventListener('click', closeModal);
</script>

ポイント: role="dialog" aria-modal="true"、開閉時のフォーカス移動、Tab のフォーカストラップ、Esc で閉じる。

フォームの例

aria-describedbyを使用したアクセシブルなフォームです。

<form class="demo-form">
    <div class="form-group">
        <label for="name">名前</label>
        <input type="text" id="name" name="name" 
               aria-describedby="name-help" required>
        <div id="name-help" class="help-text">
            フルネームを入力してください
        </div>
    </div>
    <div class="form-group">
        <label for="email">メールアドレス</label>
        <input type="email" id="email" name="email" 
               aria-describedby="email-help" required>
        <div id="email-help" class="help-text">
            有効なメールアドレスを入力してください
        </div>
    </div>
</form>

補足: この例では <label> とセットで使用しているため、aria-describedby がなくても最低限のアクセシビリティは確保されています。aria-describedby は補助的な情報(ヒントやエラーメッセージ表示など)を提供するために使用します。

まとめ

✅ やるべきこと

  1. 意味のあるHTML要素を優先する
    • <button><nav><main>などのセマンティック要素を積極的に使用
    • セマンティック要素はアクセシビリティ機能が自動で提供される
  2. role属性は必要最小限に
    • セマンティックHTMLで表現できない場合のみ使用
    • role属性だけでは不十分、JavaScriptでの実装も必要
  3. aria属性は適切に組み合わせる
    • 単体ではなく、関連する属性と組み合わせて使用
  4. 実際にテストする
    • スクリーンリーダーやキーボードナビゲーションで確認
    • 開発者ツールのAccessibilityタブでroleやfocusableを確認

❌ 避けるべきこと

  1. 過度なrole属性の使用
    • セマンティックHTMLで表現できる場合は不要
  2. aria属性の誤用
    • 仕様に沿わない使用方法は避ける
  3. テストなしでの実装
    • アクセシビリティは実装後に必ずテスト
  4. アクセシビリティの後回し
    • 開発の最初から考慮する

テスト方法

  • スクリーンリーダーでの確認
    • NVDA、JAWS、VoiceOverなどのスクリーンリーダーでテスト
  • キーボードナビゲーション
    • マウスを使わずにキーボードだけで操作できるか確認
  • ブラウザの開発者ツール
    • 要素のrole属性やaria属性が正しく設定されているか確認
  • アクセシビリティチェッカー
    • axe、WAVEなどのツールで自動チェック

参考リソース

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?