mcpField
A
accessibility

accessibility

github.com/tech-leads-clubデザイン・UI7.1k243 回インストール今日更新
リポジトリ

accessibility について

Accessibilityは、WCAG 2.1とLighthouseのアクセシビリティ指針をコンテンツやインターフェースに適用するための参照スキルです。POUR原則に沿った解説に加え、実装例と自動・手動テスト方法を示します。

主な機能

  1. 01POUR原則に基づくWCAG 2.1のガイド
  2. 02アクセシブルなUIパターンの例
  3. 03色のコントラストとキーボード操作
  4. 04自動監査コマンドの例
  5. 05手動テストのチェックリスト

活用シーン

  1. 01ページのキーボード操作とフォーカスを確認
  2. 02画像の代替テキストとフォームラベルを点検
  3. 03公開前のアクセシビリティ確認を準備

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/tech-leads-club/agent-skills --skill accessibility

インストール後、Claude Code で/accessibilityを入力して読み込まれたか確認します。

同梱ファイルreferences/WCAG.md

詳細

アクセシビリティ(a11y)

WCAG 2.1 と Lighthouse のアクセシビリティ監査に基づく包括的なアクセシビリティガイドラインです。障害のある人を含め、誰もがコンテンツを利用できるようにすることを目指します。

WCAG の原則:POUR

原則説明
Perceivable(知覚可能)さまざまな感覚を通じてコンテンツを認識できます
Operable(操作可能)すべてのユーザーがインターフェースを操作できます
Understandable(理解可能)コンテンツとインターフェースを理解できます
Robust(堅牢)コンテンツが支援技術で動作します

適合レベル

レベル要件目標
A最低限のアクセシビリティ合格必須
AA標準的な適合性合格が望ましい(多くの地域で法的要件)
AAA高度なアクセシビリティあると望ましい

知覚可能

テキストによる代替(1.1)

画像には代替テキストが必要です:


<img src="chart.png" />


<img src="chart.png" alt="Bar chart showing 40% increase in Q3 sales" />


<img src="decorative-border.png" alt="" role="presentation" />


<figure>
  <img src="infographic.png" alt="2024 market trends infographic" aria-describedby="infographic-desc" />
  <figcaption id="infographic-desc">
    
  </figcaption>
</figure>

アイコンボタンにはアクセシブルな名前が必要です:


<button>
  <svg></svg>
</button>


<button aria-label="Open menu">
  <svg aria-hidden="true"></svg>
</button>


<button>
  <svg aria-hidden="true"></svg>
  <span class="visually-hidden">Open menu</span>
</button>

視覚的に隠すクラス:

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
色のコントラスト(1.4.3、1.4.6)
テキストサイズAA 最低基準AAA 強化基準
通常のテキスト(< 18px / < 14px 太字)4.5:17:1
大きなテキスト(≥ 18px / ≥ 14px 太字)3:14.5:1
UI コンポーネントとグラフィック3:13:1
/* ❌ Low contrast (2.5:1) */
.low-contrast {
  color: #999;
  background: #fff;
}

/* ✅ Sufficient contrast (7:1) */
.high-contrast {
  color: #333;
  background: #fff;
}

/* ✅ Focus states need contrast too */
:focus-visible {
  outline: 2px solid #005fcc;
  outline-offset: 2px;
}

色だけに頼らないでください:


<input class="error-border" />
<style>
  .error-border {
    border-color: red;
  }
</style>


<div class="field-error">
  <input aria-invalid="true" aria-describedby="email-error" />
  <span id="email-error" class="error-message">
    <svg aria-hidden="true"></svg>
    Please enter a valid email address
  </span>
</div>
メディアの代替(1.2)

<video controls>
  <source src="video.mp4" type="video/mp4" />
  <track kind="captions" src="captions.vtt" srclang="en" label="English" default />
  <track kind="descriptions" src="descriptions.vtt" srclang="en" label="Descriptions" />
</video>


<audio controls>
  <source src="podcast.mp3" type="audio/mp3" />
</audio>
<details>
  <summary>Transcript</summary>
  <p>Full transcript text...</p>
</details>

操作可能

キーボードでの操作(2.1)

すべての機能をキーボードで操作できる必要があります:

// ❌ Only handles click
element.addEventListener('click', handleAction)

// ✅ Handles both click and keyboard
element.addEventListener('click', handleAction)
element.addEventListener('keydown', (e) => {
  if (e.key === 'Enter' || e.key === ' ') {
    e.preventDefault()
    handleAction()
  }
})

キーボードトラップを作らないでください:

// Modal focus management
function openModal(modal) {
  const focusableElements = modal.querySelectorAll(
    'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
  )
  const firstElement = focusableElements[0]
  const lastElement = focusableElements[focusableElements.length - 1]

  // Trap focus within modal
  modal.addEventListener('keydown', (e) => {
    if (e.key === 'Tab') {
      if (e.shiftKey && document.activeElement === firstElement) {
        e.preventDefault()
        lastElement.focus()
      } else if (!e.shiftKey && document.activeElement === lastElement) {
        e.preventDefault()
        firstElement.focus()
      }
    }
    if (e.key === 'Escape') {
      closeModal()
    }
  })

  firstElement.focus()
}
フォーカスの可視化(2.4.7)
/* ❌ Never remove focus outlines */
*:focus {
  outline: none;
}

/* ✅ Use :focus-visible for keyboard-only focus */
:focus {
  outline: none;
}

:focus-visible {
  outline: 2px solid #005fcc;
  outline-offset: 2px;
}

/* ✅ Or custom focus styles */
button:focus-visible {
  box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.5);
}
スキップリンク(2.4.1)
<body>
  <a href="#main-content" class="skip-link">Skip to main content</a>
  <header></header>
  <main id="main-content" tabindex="-1">
    
  </main>
</body>
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  background: #000;
  color: #fff;
  padding: 8px 16px;
  z-index: 100;
}

.skip-link:focus {
  top: 0;
}
時間制限(2.2)
// Allow users to extend time limits
function showSessionWarning() {
  const modal = createModal({
    title: 'Session Expiring',
    content: 'Your session will expire in 2 minutes.',
    actions: [
      { label: 'Extend session', action: extendSession },
      { label: 'Log out', action: logout },
    ],
    timeout: 120000, // 2 minutes to respond
  })
}
モーション(2.3)
/* Respect reduced motion preference */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

理解可能

ページの言語(3.1.1)

<html>
  
  <html lang="en">
    
    <p>The French word for hello is <span lang="fr">bonjour</span>.</p>
  </html>
</html>
一貫したナビゲーション(3.2.3)

<nav aria-label="Main">
  <ul>
    <li><a href="/" aria-current="page">Home</a></li>
    <li><a href="/products">Products</a></li>
    <li><a href="/about">About</a></li>
  </ul>
</nav>
フォームのラベル(3.3.2)

<input type="email" placeholder="Email" />


<label for="email">Email address</label>
<input type="email" id="email" name="email" autocomplete="email" required />


<label>
  Email address
  <input type="email" name="email" autocomplete="email" required />
</label>


<label for="password">Password</label>
<input type="password" id="password" aria-describedby="password-requirements" />
<p id="password-requirements">Must be at least 8 characters with one number.</p>
エラー処理(3.3.1、3.3.3)

<form novalidate>
  <div class="field" aria-live="polite">
    <label for="email">Email</label>
    <input type="email" id="email" aria-invalid="true" aria-describedby="email-error" />
    <p id="email-error" class="error" role="alert">Please enter a valid email address (e.g., [email protected])</p>
  </div>
</form>
// Focus first error on submit
form.addEventListener('submit', (e) => {
  const firstError = form.querySelector('[aria-invalid="true"]')
  if (firstError) {
    e.preventDefault()
    firstError.focus()

    // Announce error summary
    const errorSummary = document.getElementById('error-summary')
    errorSummary.textContent = `${errors.length} errors found. Please fix them and try again.`
    errorSummary.focus()
  }
})

堅牢

有効な HTML(4.1.1)

<div id="content">...</div>
<div id="content">...</div>


<a href="/"><button>Click</button></a>


<div id="main-content">...</div>
<div id="sidebar-content">...</div>


<a href="/" class="button-link">Click</a>
ARIA の使用(4.1.2)

ネイティブ要素を優先してください:


<div role="button" tabindex="0">Click me</div>


<button>Click me</button>


<div role="checkbox" aria-checked="false">Option</div>


<label><input type="checkbox" /> Option</label>

ARIA が必要な場合:


<div role="tablist" aria-label="Product information">
  <button role="tab" id="tab-1" aria-selected="true" aria-controls="panel-1">Description</button>
  <button role="tab" id="tab-2" aria-selected="false" aria-controls="panel-2" tabindex="-1">Reviews</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
  
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
  
</div>
ライブリージョン(4.1.3)

<div aria-live="polite" aria-atomic="true" class="status">
  
</div>


<div role="alert" aria-live="assertive">
  
</div>
// Announce dynamic content changes
function showNotification(message, type = 'polite') {
  const container = document.getElementById(`${type}-announcer`)
  container.textContent = '' // Clear first
  requestAnimationFrame(() => {
    container.textContent = message
  })
}

テストチェックリスト

自動テスト
# Lighthouse accessibility audit
npx lighthouse https://example.com --only-categories=accessibility

# axe-core
npm install @axe-core/cli -g
axe https://example.com
手動テスト
  • キーボードナビゲーション: ページ全体を Tab キーで移動し、Enter/Space キーで操作します
  • スクリーンリーダー: VoiceOver(Mac)、NVDA(Windows)、または TalkBack(Android)でテストします
  • ズーム: 200% のズームでもコンテンツを利用できることを確認します
  • ハイコントラスト: Windows のハイコントラストモードでテストします
  • モーションの軽減: prefers-reduced-motion: reduce を設定してテストします
  • フォーカス順序: 論理的で、視覚的な順序に従っていることを確認します
スクリーンリーダーのコマンド
操作VoiceOver(Mac)NVDA(Windows)
開始/停止⌘ + F5Ctrl + Alt + N
次の項目VO + →↓
前の項目VO + ←↑
有効化VO + SpaceEnter
見出し一覧VO + U、続けて矢印キーH / Shift + H
リンク一覧VO + UK / Shift + K

影響度別の一般的な問題

重大(直ちに修正)
  1. フォームのラベルがない
  2. 画像の代替テキストがない
  3. 色のコントラストが不十分
  4. キーボードトラップがある
  5. フォーカスインジケーターがない
深刻(公開前に修正)
  1. ページの言語が指定されていない
  2. 見出し構造がない
  3. リンクテキストが説明的でない
  4. メディアが自動再生される
  5. スキップリンクがない
中程度(早めに修正)
  1. アイコンに ARIA ラベルがない
  2. ナビゲーションに一貫性がない
  3. エラーが特定されていない
  4. 操作手段のない時間制限がある
  5. ランドマーク領域がない

References

コメント