Skip to content
Published on

ウェブアクセシビリティ(a11y)完全ガイド2025:WCAG 2.2、ARIA、キーボードナビゲーション

Authors

1. なぜアクセシビリティが重要(じゅうよう)か

10億人(おくにん)のユーザー

世界人口(せかいじんこう)の約(やく)15%、約(やく)10億人(おくにん)が何(なん)らかの障害(しょうがい)を持(も)っています。ウェブアクセシビリティは単(たん)に「良(よ)いこと」ではなく、ビジネス上(じょう)の必須事項(ひっすじこう)です。

障害(しょうがい)の種類(しゅるい)世界人口(せかいじんこう)ウェブへの影響(えいきょう)
視覚障害(しかくしょうがい)2.2億人(おくにん)スクリーンリーダー、拡大鏡(かくだいきょう)使用(しよう)
聴覚障害(ちょうかくしょうがい)4.66億人(おくにん)字幕(じまく)、手話(しゅわ)が必要(ひつよう)
運動障害(うんどうしょうがい)数億人(すうおくにん)キーボード、音声入力(おんせいにゅうりょく)
認知障害(にんちしょうがい)様々(さまざま)シンプルなUI、明確(めいかく)な言語(げんご)

法的要件(ほうてきようけん)

  • 米国(べいこく)ADA: ウェブサイトも「公共施設(こうきょうしせつ)」と見(み)なされる。訴訟(そしょう)が急増(きゅうぞう)
  • EU EAA (European Accessibility Act): 2025年(ねん)6月(がつ)施行(しこう)。デジタルサービスに義務(ぎむ)
  • 韓国障害者差別禁止法(かんこくしょうがいしゃさべつきんしほう): ウェブアクセシビリティ義務(ぎむ)。公共機関(こうきょうきかん)から民間(みんかん)へ拡大(かくだい)
  • WCAG 2.2: 国際標準(こくさいひょうじゅん)。ほとんどの法律(ほうりつ)がAAレベルを要求(ようきゅう)

ビジネス価値(かち)

アクセシビリティはコストではなく投資(とうし)です。

  • SEO改善(かいぜん): セマンティックHTMLとalt テキストは検索(けんさく)エンジンに好(この)まれる
  • ユーザーベース拡大(かくだい): 全人口(ぜんじんこう)の15%にリーチ
  • 法的(ほうてき)リスク軽減(けいげん): 事前投資(じぜんとうし)は訴訟費用(そしょうひよう)よりはるかに安価(あんか)
  • 全(すべ)てのユーザーのUX向上(こうじょう): キーボードショートカット、明確(めいかく)なラベルは全員(ぜんいん)に有益(ゆうえき)

2. WCAG 2.2:4つの原則(げんそく)

POUR原則(げんそく)

WCAGは4つの核心原則(かくしんげんそく)POUR(Perceivable、Operable、Understandable、Robust)に基(もと)づいています。

原則(げんそく)意味(いみ)例(れい)
Perceivable(知覚可能(ちかくかのう))情報(じょうほう)を知覚(ちかく)できることaltテキスト、字幕(じまく)、色(いろ)のコントラスト
Operable(操作可能(そうさかのう))UIを操作(そうさ)できることキーボードアクセス、十分(じゅうぶん)な時間(じかん)、発作防止(ほっさぼうし)
Understandable(理解可能(りかいかのう))コンテンツとUIを理解(りかい)できること明確(めいかく)な言語(げんご)、一貫(いっかん)したナビゲーション
Robust(堅牢(けんろう))多様(たよう)な技術(ぎじゅつ)で動作(どうさ)すること有効(ゆうこう)なHTML、ARIA互換性(ごかんせい)

適合(てきごう)レベル

  • レベルA: 最小要件(さいしょうようけん)(必須(ひっす))
  • レベルAA: ほとんどの法律(ほうりつ)が要求(ようきゅう)する水準(すいじゅん)(推奨目標(すいしょうもくひょう))
  • レベルAAA: 最高水準(さいこうすいじゅん)(サイト全体(ぜんたい)への適用(てきよう)は困難(こんなん))

WCAG 2.2の新(あたら)しい成功基準(せいこうきじゅん)

WCAG 2.2は2023年(ねん)10月(がつ)に公開(こうかい)され、以下(いか)の新(あたら)しい基準(きじゅん)が追加(ついか)されました。

成功基準(せいこうきじゅん)レベル説明(せつめい)
2.4.11 Focus Not Obscured (Minimum)AAフォーカスされた要素(ようそ)が他(た)のコンテンツに完全(かんぜん)に隠(かく)されてはならない
2.4.12 Focus Not Obscured (Enhanced)AAAフォーカスされた要素(ようそ)が部分的(ぶぶんてき)にも隠(かく)されてはならない
2.4.13 Focus AppearanceAAAフォーカスインジケータのサイズとコントラスト要件(ようけん)
2.5.7 Dragging MovementsAAドラッグが必要(ひつよう)な機能(きのう)に代替手段(だいたいしゅだん)を提供(ていきょう)
2.5.8 Target Size (Minimum)AAタッチターゲットが最低(さいてい)24x24 CSSピクセル
3.2.6 Consistent HelpAヘルプメカニズムが一貫(いっかん)した位置(いち)に
3.3.7 Redundant EntryA以前入力(いぜんにゅうりょく)した情報(じょうほう)を再要求(さいようきゅう)しない
3.3.8 Accessible AuthenticationAA認知機能(にんちきのう)テストなしの認証(にんしょう)
3.3.9 Accessible Authentication (Enhanced)AAAより厳格(げんかく)な認証(にんしょう)アクセシビリティ

3. セマンティックHTML:アクセシビリティの基礎(きそ)

正(ただ)しい要素(ようそ)の使用(しよう)

セマンティックHTMLはアクセシビリティの80%を解決(かいけつ)します。スクリーンリーダーはHTML要素(ようそ)の意味(いみ)を理解(りかい)します。

<!-- 悪い例:divで全てを作る -->
<div class="button" onclick="submit()">送信</div>
<div class="header">サイトタイトル</div>
<div class="nav">
  <div class="link" onclick="goto('/')">ホーム</div>
</div>

<!-- 良い例:セマンティック要素を使用 -->
<button type="submit">送信</button>
<header><h1>サイトタイトル</h1></header>
<nav>
  <a href="/">ホーム</a>
</nav>

<button>は自動的(じどうてき)にキーボードフォーカスを受(う)け取(と)り、Enter/Spaceで有効化(ゆうこうか)され、スクリーンリーダーが「ボタン」と認識(にんしき)します。<div>ではこれらすべてを手動(しゅどう)で実装(じっそう)する必要(ひつよう)があります。

ランドマーク

<body>
  <header>
    <nav aria-label="メインナビゲーション">...</nav>
  </header>

  <main>
    <article>
      <h1>記事タイトル</h1>
      <section aria-labelledby="section-1">
        <h2 id="section-1">セクション1</h2>
        ...
      </section>
    </article>

    <aside aria-label="関連リンク">...</aside>
  </main>

  <footer>...</footer>
</body>

スクリーンリーダーのユーザーはランドマーク間(かん)を素早(すばや)く移動(いどう)できます。VoiceOverのRotorを使(つか)えば、header、nav、main、footerへ即座(そくざ)にジャンプできます。

見出(みだ)し階層構造(かいそうこうぞう)

<!-- 悪い例:見出しレベルのスキップ -->
<h1>ページタイトル</h1>
<h3>サブセクション</h3>  <!-- h2をスキップ! -->
<h5>詳細項目</h5>         <!-- h4をスキップ! -->

<!-- 良い例:順次的な見出し構造 -->
<h1>ページタイトル</h1>
  <h2>セクションA</h2>
    <h3>サブセクションA-1</h3>
    <h3>サブセクションA-2</h3>
  <h2>セクションB</h2>
    <h3>サブセクションB-1</h3>

スクリーンリーダーユーザーの67%が見出(みだ)しでページを探索(たんさく)します。見出(みだ)しレベルをスキップすると文書構造(ぶんしょこうぞう)の把握(はあく)が困難(こんなん)になります。


4. ARIA:アクセシブルな意味(いみ)の追加(ついか)

ARIAの第一(だいいち)ルール

ARIAの第一(だいいち)ルール:ARIAを使(つか)わないこと。 ネイティブHTML要素(ようそ)で十分(じゅうぶん)な場合(ばあい)、ARIAは不要(ふよう)です。

<!-- ARIA不要:ネイティブ要素で十分 -->
<button>削除</button>                    <!-- role="button" 不要 -->
<input type="checkbox" />                <!-- role="checkbox" 不要 -->
<nav>                                    <!-- role="navigation" 不要 -->

<!-- ARIA必要:ネイティブ要素がない場合 -->
<div role="tablist">
  <button role="tab" aria-selected="true">タブ1</button>
  <button role="tab" aria-selected="false">タブ2</button>
</div>
<div role="tabpanel">タブ1のコンテンツ</div>

主要(しゅよう)なARIA属性(ぞくせい)

<!-- aria-label: 視覚的テキストのない要素にラベルを提供 -->
<button aria-label="メニューを閉じる">
  <svg><!-- Xアイコン --></svg>
</button>

<!-- aria-labelledby: 他の要素のテキストをラベルとして参照 -->
<h2 id="cart-heading">ショッピングカート</h2>
<ul aria-labelledby="cart-heading">
  <li>商品1</li>
  <li>商品2</li>
</ul>

<!-- aria-describedby: 追加説明の関連付け -->
<input
  type="password"
  aria-describedby="pw-hint"
/>
<p id="pw-hint">8文字以上、特殊文字を含む</p>

<!-- aria-live: 動的コンテンツの変更を通知 -->
<div aria-live="polite">
  カートに3つの商品があります。
</div>

<!-- aria-expanded: 展開/折りたたみ状態 -->
<button aria-expanded="false" aria-controls="menu">
  メニュー
</button>
<ul id="menu" hidden>...</ul>

<!-- aria-hidden: スクリーンリーダーから非表示 -->
<span aria-hidden="true">🔥</span>
<span class="sr-only">人気</span>

ライブリージョン

<!-- aria-live="polite": 現在の読み上げ完了後に通知 -->
<div aria-live="polite" aria-atomic="true">
  検索結果:42件
</div>

<!-- aria-live="assertive": 即座に通知(エラーなど) -->
<div role="alert" aria-live="assertive">
  セッションが期限切れです。再度ログインしてください。
</div>

<!-- role="status": ステータスメッセージ(politeと類似) -->
<div role="status">
  ファイルアップロード完了
</div>

<!-- role="log": チャットメッセージなど -->
<div role="log" aria-live="polite">
  <!-- 新しいメッセージが追加される -->
</div>

5. キーボードアクセシビリティ

フォーカス管理(かんり)の基本(きほん)

すべてのインタラクティブ要素(ようそ)はキーボードでアクセス可能(かのう)でなければなりません。

キー動作(どうさ)
Tab次(つぎ)のフォーカス可能(かのう)な要素(ようそ)へ移動(いどう)
Shift + Tab前(まえ)のフォーカス可能(かのう)な要素(ようそ)へ移動(いどう)
Enterリンクの有効化(ゆうこうか)、ボタンのクリック
Spaceボタンのクリック、チェックボックスのトグル
Escapeモーダル/ポップアップを閉(と)じる
矢印(やじるし)キーメニュー、タブ、ラジオグループ内(ない)の移動(いどう)

スキップリンク

<!-- ページの最上部に配置 -->
<a href="#main-content" class="skip-link">
  メインコンテンツへスキップ
</a>

<nav>
  <!-- 長いナビゲーションメニュー -->
</nav>

<main id="main-content" tabindex="-1">
  <!-- メインコンテンツ -->
</main>
.skip-link {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.skip-link:focus {
  position: fixed;
  top: 10px;
  left: 10px;
  width: auto;
  height: auto;
  padding: 12px 24px;
  background: #000;
  color: #fff;
  z-index: 9999;
  font-size: 1rem;
}

フォーカストラップ

モーダルが開(ひら)いているとき、フォーカスはモーダル内(ない)でのみ循環(じゅんかん)しなければなりません。

function useFocusTrap(containerRef: React.RefObject<HTMLElement>) {
  useEffect(() => {
    const container = containerRef.current
    if (!container) return

    const focusableSelector = [
      'a[href]',
      'button:not([disabled])',
      'input:not([disabled])',
      'select:not([disabled])',
      'textarea:not([disabled])',
      '[tabindex]:not([tabindex="-1"])',
    ].join(', ')

    const focusableElements = container.querySelectorAll(focusableSelector)
    const firstElement = focusableElements[0] as HTMLElement
    const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement

    function handleKeyDown(e: KeyboardEvent) {
      if (e.key !== 'Tab') return

      if (e.shiftKey) {
        if (document.activeElement === firstElement) {
          e.preventDefault()
          lastElement.focus()
        }
      } else {
        if (document.activeElement === lastElement) {
          e.preventDefault()
          firstElement.focus()
        }
      }
    }

    container.addEventListener('keydown', handleKeyDown)
    firstElement?.focus()

    return () => container.removeEventListener('keydown', handleKeyDown)
  }, [containerRef])
}

タブ順序管理(じゅんじょかんり)

<!-- tabindexの値 -->
<!-- tabindex="0": 自然な順序に含める -->
<div role="button" tabindex="0">カスタムボタン</div>

<!-- tabindex="-1": プログラム的にのみフォーカス可能 -->
<div id="error-message" tabindex="-1">エラーが発生しました!</div>

<!-- 正の値のtabindexは使用禁止!順序が崩れます -->
<!-- 悪い例: tabindex="1", tabindex="2", tabindex="3" -->

6. 色(いろ)とコントラスト

コントラスト比(ひ)の要件(ようけん)

テキストの種類(しゅるい)AAレベルAAAレベル
通常(つうじょう)テキスト(14px未満(みまん))4.5:17:1
大(おお)きいテキスト(18px以上(いじょう)または14pxボールド)3:14.5:1
UIコンポーネント、グラフィック3:1-

CSSでコントラストを確保(かくほ)

/* 良いコントラスト: #333 on #fff = 12.63:1 */
body {
  color: #333333;
  background-color: #ffffff;
}

/* リンク: 周囲テキストと3:1のコントラスト + 下線または他の視覚的手がかり */
a {
  color: #0066cc;
  text-decoration: underline;
}

/* フォーカスインジケータ: 3:1のコントラストが必須 */
:focus-visible {
  outline: 3px solid #1a73e8;
  outline-offset: 2px;
}

/* ダークモードでもコントラストを維持 */
@media (prefers-color-scheme: dark) {
  body {
    color: #e0e0e0;
    background-color: #121212;
  }

  a {
    color: #8ab4f8;
  }
}

色覚異常(しきかくいじょう)への対応(たいおう)

/* 色だけで情報を伝えない */

/* 悪い例: 色だけでエラーを示す */
.error-field {
  border-color: red;
}

/* 良い例: 色 + アイコン + テキスト */
.error-field {
  border-color: #d32f2f;
  border-width: 2px;
}

.error-field::before {
  content: "⚠ ";
}

.error-message {
  color: #d32f2f;
  font-weight: bold;
}

コントラスト確認(かくにん)ツール

  • Chrome DevTools: 要素(ようそ)の検査時(けんさじ)にコントラスト比(ひ)を表示(ひょうじ)
  • axe DevTools: ページ全体(ぜんたい)のコントラスト監査(かんさ)
  • Colour Contrast Analyser (CCA): スタンドアロンツール
  • Stark: Figma/Sketchプラグイン

7. 画像(がぞう)とメディア

altテキストガイド

<!-- 情報性画像: 内容を説明 -->
<img src="chart.png" alt="2025年の売上推移:第1四半期100万、第2四半期150万、第3四半期200万" />

<!-- 装飾的画像: 空のalt -->
<img src="decorative-line.png" alt="" />

<!-- 機能性画像(リンク/ボタン): 動作を説明 -->
<a href="/home">
  <img src="logo.png" alt="ホームページへ移動" />
</a>

<!-- 複雑な画像: 長い説明を提供 -->
<figure>
  <img src="infographic.png" alt="アクセシビリティ統計インフォグラフィック" aria-describedby="info-desc" />
  <figcaption id="info-desc">
    世界で10億人が障害を持っており、
    ウェブサイトの97%がアクセシビリティエラーを含んでいます。
    最も一般的なエラーは低い色のコントラスト(83%)です。
  </figcaption>
</figure>

<!-- SVGのアクセシビリティ -->
<svg role="img" aria-labelledby="svg-title">
  <title id="svg-title">ダウンロードアイコン</title>
  <path d="..." />
</svg>

動画(どうが)のアクセシビリティ

<video controls>
  <source src="tutorial.mp4" type="video/mp4" />
  <!-- 字幕(キャプション) -->
  <track kind="captions" src="captions-ja.vtt" srclang="ja" label="日本語" default />
  <track kind="captions" src="captions-en.vtt" srclang="en" label="English" />
  <!-- 音声解説 -->
  <track kind="descriptions" src="descriptions-ja.vtt" srclang="ja" label="音声解説" />
</video>

音声(おんせい)コンテンツ

すべての音声(おんせい)コンテンツにはテキスト代替(だいたい)(トランスクリプト)が必要(ひつよう)です。


8. フォームのアクセシビリティ

ラベルと入力(にゅうりょく)の関連付(かんれんづ)け

<!-- 方法1: for/id関連付け(推奨) -->
<label for="email">メールアドレス</label>
<input type="email" id="email" name="email" autocomplete="email" />

<!-- 方法2: labelで囲む -->
<label>
  メールアドレス
  <input type="email" name="email" autocomplete="email" />
</label>

<!-- 方法3: aria-labelledby -->
<span id="email-label">メールアドレス</span>
<input type="email" aria-labelledby="email-label" autocomplete="email" />

エラーメッセージとバリデーション

<div class="form-group">
  <label for="password">パスワード</label>
  <input
    type="password"
    id="password"
    aria-describedby="pw-requirements pw-error"
    aria-invalid="true"
    autocomplete="new-password"
  />
  <p id="pw-requirements" class="hint">
    8文字以上、大文字・小文字・数字・特殊文字を含む
  </p>
  <p id="pw-error" class="error" role="alert">
    パスワードが要件を満たしていません。
  </p>
</div>

必須(ひっす)フィールド

<!-- aria-required + 視覚的表示 -->
<label for="name">
  名前 <span aria-hidden="true" class="required">*</span>
</label>
<input
  type="text"
  id="name"
  required
  aria-required="true"
  autocomplete="name"
/>
<p class="form-note">*は必須項目です</p>

オートコンプリート

<!-- WCAG 1.3.5: autocomplete属性の使用 -->
<input type="text" autocomplete="given-name" />   <!-- 名 -->
<input type="text" autocomplete="family-name" />   <!-- 姓 -->
<input type="email" autocomplete="email" />         <!-- メール -->
<input type="tel" autocomplete="tel" />             <!-- 電話番号 -->
<input type="text" autocomplete="street-address" /> <!-- 住所 -->

9. React/Next.jsアクセシビリティパターン

SPAでのフォーカス管理(かんり)

SPA(Single Page Application)では、ページ遷移時(せんいじ)にフォーカスが自動的(じどうてき)に移動(いどう)しません。

// ルート変更時にフォーカスを移動
function useRouteAnnounce() {
  const pathname = usePathname()

  useEffect(() => {
    // メインコンテンツへフォーカスを移動
    const main = document.querySelector('main')
    if (main) {
      main.setAttribute('tabindex', '-1')
      main.focus()
    }
  }, [pathname])

  return (
    <div
      role="status"
      aria-live="polite"
      className="sr-only"
    >
      ページが読み込まれました
    </div>
  )
}

ルート変更(へんこう)の通知(つうち)

// Next.js App Router: ルート変更の通知
'use client'
import { usePathname } from 'next/navigation'
import { useEffect, useState } from 'react'

function RouteAnnouncer() {
  const pathname = usePathname()
  const [announcement, setAnnouncement] = useState('')

  useEffect(() => {
    const pageTitle = document.title
    setAnnouncement(`${pageTitle}ページに移動しました`)
  }, [pathname])

  return (
    <div
      role="status"
      aria-live="assertive"
      aria-atomic="true"
      className="sr-only"
    >
      {announcement}
    </div>
  )
}

アクセシブルなモーダル(Dialog)

'use client'
import { useEffect, useRef } from 'react'

interface DialogProps {
  isOpen: boolean
  onClose: () => void
  title: string
  children: React.ReactNode
}

function AccessibleDialog({ isOpen, onClose, title, children }: DialogProps) {
  const dialogRef = useRef<HTMLDialogElement>(null)
  const previousFocusRef = useRef<HTMLElement | null>(null)

  useEffect(() => {
    const dialog = dialogRef.current
    if (!dialog) return

    if (isOpen) {
      previousFocusRef.current = document.activeElement as HTMLElement
      dialog.showModal()
    } else {
      dialog.close()
      previousFocusRef.current?.focus()
    }
  }, [isOpen])

  return (
    <dialog
      ref={dialogRef}
      aria-labelledby="dialog-title"
      onClose={onClose}
    >
      <h2 id="dialog-title">{title}</h2>
      {children}
      <button onClick={onClose}>閉じる</button>
    </dialog>
  )
}

Radix UI / Headless UIの活用(かつよう)

import * as Dialog from '@radix-ui/react-dialog'

// Radix UIはアクセシビリティを自動的に処理します
function MyDialog() {
  return (
    <Dialog.Root>
      <Dialog.Trigger asChild>
        <button>プロフィール編集</button>
      </Dialog.Trigger>
      <Dialog.Portal>
        <Dialog.Overlay className="overlay" />
        <Dialog.Content className="content">
          <Dialog.Title>プロフィール編集</Dialog.Title>
          <Dialog.Description>
            プロフィール情報を変更してください。
          </Dialog.Description>
          {/* フォームフィールド */}
          <Dialog.Close asChild>
            <button aria-label="閉じる">X</button>
          </Dialog.Close>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  )
}

スクリーンリーダー専用(せんよう)テキスト

/* sr-onlyユーティリティクラス */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}
// 使用例
<button>
  <TrashIcon />
  <span className="sr-only">アイテムを削除</span>
</button>

<a href="/cart">
  <CartIcon />
  <span className="sr-only">ショッピングカート(3つの商品)</span>
</a>

10. テストと自動化(じどうか)

axe-coreで自動(じどう)テスト

// jest + axe-core
import { render } from '@testing-library/react'
import { axe, toHaveNoViolations } from 'jest-axe'

expect.extend(toHaveNoViolations)

describe('Button', () => {
  it('アクセシビリティ違反がないこと', async () => {
    const { container } = render(<Button>クリック</Button>)
    const results = await axe(container)
    expect(results).toHaveNoViolations()
  })
})

Playwright + axe統合(とうごう)テスト

import { test, expect } from '@playwright/test'
import AxeBuilder from '@axe-core/playwright'

test('ホームページのアクセシビリティ', async ({ page }) => {
  await page.goto('/')

  const accessibilityScanResults = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag22aa'])
    .analyze()

  expect(accessibilityScanResults.violations).toEqual([])
})

// キーボードナビゲーションテスト
test('キーボードでメニューを操作', async ({ page }) => {
  await page.goto('/')

  // Tabでスキップリンクへ移動
  await page.keyboard.press('Tab')
  const skipLink = page.getByText('メインコンテンツへスキップ')
  await expect(skipLink).toBeFocused()

  // Enterでスキップリンクを有効化
  await page.keyboard.press('Enter')
  const main = page.locator('main')
  await expect(main).toBeFocused()
})

Lighthouseアクセシビリティスコア

# CIでLighthouseを実行
npx lighthouse http://localhost:3000 \
  --only-categories=accessibility \
  --output=json \
  --output-path=./lighthouse-report.json
// CIパイプラインでアクセシビリティスコアを確認
const report = JSON.parse(fs.readFileSync('./lighthouse-report.json', 'utf-8'))
const accessibilityScore = report.categories.accessibility.score * 100

if (accessibilityScore < 90) {
  console.error(`アクセシビリティスコア ${accessibilityScore}点 - 90点以上が必要です`)
  process.exit(1)
}

スクリーンリーダーの手動(しゅどう)テストチェックリスト

タスクVoiceOver (Mac)NVDA (Windows)TalkBack (Android)
ページタイトルの読(よ)み上(あ)げCmd + F5Insert + T自動(じどう)
ランドマーク探索(たんさく)Rotor (VO + U)D/Shift+Dスワイプ
見出(みだ)し探索(たんさく)VO + Cmd + HH/Shift+Hスワイプ
フォームフィールドVO + TabTabタッチ探索(たんさく)
リンク一覧(いちらん)RotorInsert + F7メニュー

11. 法的要件(ほうてきようけん)と規制(きせい)

米国(べいこく):ADAとSection 508

  • ADA Title III: ウェブサイトは「公共施設(こうきょうしせつ)」に該当(がいとう)。WCAG 2.1 AA要求(ようきゅう)
  • Section 508: 連邦政府(れんぽうせいふ)ウェブサイトに義務(ぎむ)。WCAG 2.0 AA基準(きじゅん)
  • 訴訟動向(そしょうどうこう): 2023年(ねん)にウェブアクセシビリティ訴訟(そしょう)4,600件以上(いじょう)

EU:European Accessibility Act (EAA)

  • 2025年(ねん)6月(がつ)28日(にち) 施行(しこう)
  • デジタルサービスを提供(ていきょう)する企業(きぎょう)が対象(たいしょう)
  • WCAG 2.1 AA以上(いじょう)を要求(ようきゅう)
  • 違反時(いはんじ)に罰金(ばっきん)

韓国(かんこく):障害者差別禁止法(しょうがいしゃさべつきんしほう)

  • 障害者差別禁止(しょうがいしゃさべつきんし)および権利救済(けんりきゅうさい)に関(かん)する法律(ほうりつ)(2008年(ねん))
  • ウェブアクセシビリティ認証(にんしょう)マーク
  • 公共機関(こうきょうきかん)に義務(ぎむ)、民間(みんかん)セクターへ拡大(かくだい)
  • KWCAG 2.2: WCAG 2.2に基(もと)づく

アクセシビリティステートメント

<!-- ウェブサイトにアクセシビリティステートメントを含めることを推奨 -->
<h1>アクセシビリティステートメント</h1>
<p>
  私たちはWCAG 2.2 AA基準に準拠し、
  すべてのユーザーにアクセシブルなウェブ体験を
  提供することに取り組んでいます。
</p>
<p>
  アクセシビリティに関する問題を発見された場合は、
  <a href="mailto:a11y@example.com">a11y@example.com</a>まで
  ご連絡ください。
</p>

12. クイズ

Q1. WCAGの4つの原則(げんそく)(POUR)を説明(せつめい)し、それぞれの例(れい)を挙(あ)げてください。

Perceivable(知覚可能(ちかくかのう)):すべての情報(じょうほう)をユーザーに提示(ていじ)できなければなりません。画像(がぞう)のaltテキストの提供(ていきょう)、動画(どうが)への字幕(じまく)追加(ついか)が例(れい)です。Operable(操作可能(そうさかのう)):すべての機能(きのう)が操作(そうさ)できなければなりません。キーボードのみでの全(すべ)ての機能(きのう)の使用(しよう)、十分(じゅうぶん)な時間(じかん)の提供(ていきょう)が例(れい)です。Understandable(理解可能(りかいかのう)):コンテンツとUIを理解(りかい)できなければなりません。明確(めいかく)なエラーメッセージ、一貫(いっかん)したナビゲーションが例(れい)です。Robust(堅牢(けんろう)):多様(たよう)な技術(ぎじゅつ)で動作(どうさ)しなければなりません。有効(ゆうこう)なHTML、支援技術(しえんぎじゅつ)との互換性(ごかんせい)が例(れい)です。

Q2. ARIAの第一(だいいち)ルールとは何(なん)ですか、なぜ重要(じゅうよう)ですか?

ARIAの第一(だいいち)ルールは「ネイティブHTML要素(ようそ)で十分(じゅうぶん)な場合(ばあい)はARIAを使用(しよう)しないこと」です。例(たと)えば、button要素(ようそ)にrole="button"を追加(ついか)することは不要(ふよう)です。ネイティブHTML要素(ようそ)にはアクセシブルなセマンティクス、キーボード動作(どうさ)、フォーカス管理(かんり)がすでに組(く)み込(こ)まれているためです。ARIAを誤(あやま)って使用(しよう)すると、アクセシビリティを改善(かいぜん)するどころか悪化(あっか)させる可能性(かのうせい)があります。

Q3. コントラスト比(ひ)4.5:1と3:1はそれぞれどのような状況(じょうきょう)に適用(てきよう)されますか?

4.5:1は通常(つうじょう)サイズのテキスト(18px未満(みまん))に対(たい)するAAレベルの要件(ようけん)です。3:1は大(おお)きなテキスト(18px以上(いじょう)または14pxボールド)およびUIコンポーネント(ボタンの境界線(きょうかいせん)、入力(にゅうりょく)フィールドなど)に対(たい)するAAレベルの要件(ようけん)です。AAAレベルは通常(つうじょう)テキストに7:1、大(おお)きなテキストに4.5:1を要求(ようきゅう)します。

Q4. SPAでルート変更時(へんこうじ)のアクセシビリティをどのように保証(ほしょう)しますか?

SPAのクライアントサイドルーティングは、従来(じゅうらい)のページロードとは異(こと)なり、スクリーンリーダーに自動通知(じどうつうち)を提供(ていきょう)しません。解決策(かいけつさく)として、ルート変更時(へんこうじ)にメインコンテンツへのフォーカス移動(いどう)、aria-liveリージョンでの新(あたら)しいページタイトルの通知(つうち)、documentのtitle更新(こうしん)、スキップリンクの提供(ていきょう)があります。Next.jsのApp Routerは組(く)み込(こ)みのルート通知機能(つうちきのう)を提供(ていきょう)しています。

Q5. axe-coreをCI/CDパイプラインに統合(とうごう)する方法(ほうほう)とその限界(げんかい)は?

axe-coreはjest-axe(ユニットテスト)や@axe-core/playwright(E2Eテスト)でCIに統合(とうごう)できます。WCAGタグでフィルタリングして特定(とくてい)の基準(きじゅん)のみをテストし、違反(いはん)があればビルドを失敗(しっぱい)させます。限界(げんかい)として、自動(じどう)ツールはアクセシビリティの問題(もんだい)の約(やく)30-40%しか検出(けんしゅつ)できないという点(てん)があります。キーボードの使(つか)いやすさ、スクリーンリーダーとの互換性(ごかんせい)、認知的(にんちてき)アクセシビリティなどは手動(しゅどう)テストが必須(ひっす)です。


参考資料(さんこうしりょう)

  1. WCAG 2.2 - W3C Recommendation
  2. WAI-ARIA 1.2 Specification
  3. MDN Web Accessibility Guide
  4. A11y Project Checklist
  5. Deque axe-core
  6. WebAIM Million Report
  7. Radix UI Accessibility
  8. React Accessibility Docs
  9. Next.js Accessibility
  10. Inclusive Components by Heydon Pickering
  11. EU European Accessibility Act
  12. 韓国(かんこく)ウェブアクセシビリティ認証評価院(にんしょうひょうかいん)
  13. Chrome DevTools Accessibility
  14. Stark Accessibility Tools