mcpField
A
accessibility

accessibility

github.com/davila7デザイン・UI33.0k358 回インストール1 日前に更新
リポジトリ

accessibility について

accessibilityは、WCAG 2.1とLighthouseのアクセシビリティ監査ガイドに基づく、デザイナーや開発者向けのスキルです。アクセシビリティの原則や実装例に加え、自動テストと手動テストの方法を扱います。

主な機能

  1. 01WCAGのPOUR原則の適用
  2. 02コントラストとテキスト代替の確認
  3. 03キーボード操作可能なUI設計
  4. 04フォーカス・フォーム・エラーへの対応
  5. 05自動および手動チェックの実施

活用シーン

  1. 01WCAGガイドに沿ったページ監査
  2. 02キーボード操作とフォーカス表示の改善
  3. 03フォームラベルとエラー通知の確認
  4. 04アクセシビリティツールでページをテスト

インストール

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

$npx skills add https://github.com/davila7/claude-code-templates --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>
一貫したナビゲーション(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. ランドマークリージョンの欠落

参考資料

コメント