mcpField
W
web-accessibility

web-accessibility

github.com/s-hiraokuデザイン・UI1878 回インストール3 週間前に更新
リポジトリ

web-accessibility について

Web開発者向けのアクセシビリティスキルです。セマンティックHTML、キーボード操作、ARIA、フォーム、タッチ操作、国際化の実装とレビューを案内します。体系的な確認に使えるチェックリストも含みます。

主な機能

  1. 01セマンティックなHTML要素の選択
  2. 02キーボード操作と可視フォーカスへの対応
  3. 03動的コンテンツへのARIAの使用
  4. 04フォームのラベルとエラーの関連付け
  5. 05タッチターゲットとロケール形式の確認
  6. 06チェックリストによる実装レビュー

活用シーン

  1. 01キーボードで操作できるインターフェースの設計
  2. 02フォームのラベルとエラーメッセージの確認
  3. 03動的な通知をスクリーンリーダーに伝達
  4. 04モバイルのタッチ領域とフォーカス表示の確認

インストール

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

$npx skills add https://github.com/s-hiraoku/synapse-a2a --skill web-accessibility

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

詳細

ウェブアクセシビリティ

誰もが利用できるインターフェースを構築しましょう。これらは任意の追加機能ではなく、品質の基本です。

セマンティック HTML

用途に合った要素を使いましょう。インタラクティブ要素を<div>で模倣してはいけません。

// BAD: div with click handler
<div onClick={handleClick} className="button">Submit</div>

// GOOD: semantic button
<button onClick={handleClick}>Submit</button>

// BAD: div as link
<div onClick={() => router.push('/about')}>About</div>

// GOOD: anchor/Link for navigation
<Link href="/about">About</Link>
要素の選択ガイド
目的要素使用しない要素
アクション(送信、切り替え、削除)<button><div onClick>
URL へのナビゲーション<a> / <Link><button onClick={navigate}>
フォーム入力<input>、<select>、<textarea><div>ベースのカスタム入力
セクション見出し<h1>~<h6>(順番に使用)<div className="heading">
項目のリスト<ul> / <ol> + <li>繰り返し使用する<div>
ナビゲーショングループ<nav><div className="nav">
メインコンテンツ<main><div id="content">

キーボードナビゲーション

すべてのインタラクティブ要素はキーボードで操作できる必要があります。

フォーカス管理
/* NEVER remove focus indicators without replacement */
/* BAD */
*:focus { outline: none; }

/* GOOD: Visible focus only on keyboard navigation */
.interactive:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Group focus for compound controls */
.input-group:focus-within {
  outline: 2px solid var(--color-accent);
}
キーボードイベントの処理
// Interactive custom elements need keyboard support
function CustomButton({ onClick, children }: { onClick: () => void; children: React.ReactNode }) {
  return (
    <div
      role="button"
      tabIndex={0}
      onClick={onClick}
      onKeyDown={(e) => {
        if (e.key === 'Enter' || e.key === ' ') {
          e.preventDefault();
          onClick();
        }
      }}
    >
      {children}
    </div>
  );
}
// Better: just use <button> and avoid all of the above
スキップリンク

キーボードユーザー向けにスキップナビゲーションを用意しましょう。

<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50">
  Skip to main content
</a>

スクロール先として使用する見出しには、固定ヘッダーを考慮したオフセットが必要です。

[id] { scroll-margin-top: 5rem; }

ARIA パターン

アイコンボタン
// Icon-only buttons MUST have aria-label
<button aria-label="Close dialog" onClick={onClose}>
  <XIcon aria-hidden="true" />
</button>

// Decorative icons are hidden from screen readers
<span aria-hidden="true">🔒</span> Secure connection
ライブリージョン

動的なコンテンツの変更をスクリーンリーダーに通知します。

// Toast notifications
<div role="status" aria-live="polite">
  {notification && <p>{notification.message}</p>}
</div>

// Error alerts
<div role="alert" aria-live="assertive">
  {error && <p>{error.message}</p>}
</div>
読み込み状態
<button disabled={isLoading} aria-busy={isLoading}>
  {isLoading ? 'Saving\u2026' : 'Save'}  {/* proper ellipsis character */}
</button>

// Skeleton screens
<div aria-busy="true" aria-label="Loading content">
  <Skeleton />
</div>

フォーム

ラベル

すべての入力には関連付けられたラベルが必要です。

// GOOD: Explicit association
<label htmlFor="email">Email</label>
<input id="email" type="email" autoComplete="email" />

// GOOD: Wrapping (clickable label, no htmlFor needed)
<label>
  Email
  <input type="email" autoComplete="email" />
</label>

// GOOD: Visually hidden but accessible
<label htmlFor="search" className="sr-only">Search</label>
<input id="search" type="search" placeholder="Search..." />
入力タイプとオートコンプリート

適切なモバイルキーボードとブラウザーの動作を利用するため、セマンティックな入力タイプを使いましょう。

<input type="email" autoComplete="email" />
<input type="tel" autoComplete="tel" />
<input type="url" autoComplete="url" />
<input type="password" autoComplete="current-password" />
<input type="password" autoComplete="new-password" />
検証とエラー
<div>
  <label htmlFor="email">Email</label>
  <input
    id="email"
    type="email"
    aria-invalid={!!errors.email}
    aria-describedby={errors.email ? 'email-error' : undefined}
  />
  {errors.email && (
    <p id="email-error" role="alert" className="text-red-600">
      {errors.email}
    </p>
  )}
</div>
フォームの動作ルール
  • すべての入力でペーストを禁止しない
  • メールアドレスとコードではスペルチェックを無効にする:spellCheck={false}
  • リクエストが開始するまで送信ボタンを有効にしておく。読み込み中はスピナーを表示する
  • 送信失敗時は最初のエラーにフォーカスする
  • チェックボックスとラジオボタン:ラベルと入力の間に操作できない領域を作らず、クリック対象を一体化する

画像とメディア

// Informative images: descriptive alt
<img src="chart.png" alt="Revenue grew 40% from Q1 to Q3 2025" />

// Decorative images: empty alt
<img src="divider.svg" alt="" />

// Prevent layout shift: always set dimensions
<img src="photo.jpg" width={800} height={600} alt="Team photo" />

// Below fold: lazy load
<img src="photo.jpg" loading="lazy" alt="..." />

// Critical: prioritize
<img src="hero.jpg" fetchPriority="high" alt="..." />

タッチ操作とモバイル

タッチターゲット

すべてのインタラクティブ要素は最小44x44pxにします(WCAG 2.5.5)。

.touch-target {
  min-height: 44px;
  min-width: 44px;
}
セーフエリア

デバイスのノッチとホームインジケーターに対応します。

.full-bleed {
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}
タッチ操作
/* Prevent 300ms delay and highlight flash */
.interactive {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* Prevent scroll chaining on modals */
.modal { overscroll-behavior: contain; }

国際化

// BAD: Hardcoded formats
const date = `${month}/${day}/${year}`;
const price = `$${amount.toFixed(2)}`;

// GOOD: Locale-aware formatting
const date = new Intl.DateTimeFormat(locale).format(new Date());
const price = new Intl.NumberFormat(locale, {
  style: 'currency',
  currency: 'USD',
}).format(amount);

// Detect language
const lang = request.headers.get('accept-language')?.split(',')[0] ?? 'en';

アクセシビリティのためのパフォーマンス

  • 50項目を超えるリストは仮想化する
  • 重要なフォントにはfont-display: swapを指定した<link rel="preload" as="font">を使用する
  • レンダリング中のレイアウト読み取りを避ける(スクリーンリーダーユーザーにとっても動作がカクつく原因になります)
  • 大規模なフォームでは、非制御入力のほうが制御入力よりもパフォーマンスに優れます

チェックリスト

レビュー時に使用してください。

  • すべてのインタラクティブ要素をキーボードで操作できる
  • :focus-visibleでフォーカスインジケーターが表示される
  • 色のコントラストがWCAG AAを満たしている(本文は4.5:1、大きな文字は3:1)
  • 画像に適切な代替テキストがある
  • フォーム入力にラベルがある
  • エラーメッセージがaria-describedbyで入力に関連付けられている
  • アイコンのみのボタンにaria-labelがある
  • 動的コンテンツにaria-liveリージョンを使用している
  • タッチターゲットが最小44x44pxである
  • prefers-reduced-motionに対応している
  • 代替のフォーカススタイルなしにoutline: noneを使用していない
  • 見出し階層が順番どおりである(レベルを飛ばしていない)

コメント