Communitygithub.com

フロントエンドアクセシビリティパターン集

React と Next.js 向けのアクセシビリティパターン集。セマンティックHTML、ARIA属性、フォームラベリング、キーボードナビゲーション、フォーカス管理、スクリーンリーダーサポートをカバー。フォーム、モーダル、ドロップダウン、ツールチップ、タブの構築・レビュー時や、a11y リンター・コードレビューの指摘修正、キーボードナビゲーション・フォーカス管理の実装時に活用。

フロントエンドアクセシビリティパターン集 とは?

このスキル「フロントエンドアクセシビリティパターン集」は、React/Next.js 開発者がコードレビューで頻繁に指摘されるアクセシビリティ問題を体系的に解決するための実践的なリファレンスです。フォームラベルの欠如、誤ったARIA属性の使用、非セマンティックなインタラクティブ要素、壊れたキーボードナビゲーションといった典型的な問題に対して、具体的なコード例(良い例・悪い例)を示しながら修正方法を解説します。特に `htmlFor`/`id` によるラベル接続、`aria-describedby` を使ったエラーメッセージ連携、`aria-expanded`/`aria-controls` によるアコーディオン制御、カスタムドロップダウンのキーボード操作(ArrowUp/Down, Enter, Escape)、モーダルのフォーカス保存・復元など、実装で迷いやすいポイントを網羅。チェックリストも付属しており、コンポーネントをレビューに出す前に確認すべき項目を一覧できます。対応プラットフォームは React / Next.js で、リンターやAIコードレビューツール(CodeRabbit, ESLint a11y)と合わせて使うことで効果が最大化します。 キーワード補足: - `aria-*` 属性: ARIA属性。アクセシブルリッチインターネットアプリケーションの略で、HTML要素にアクセシビリティ情報を追加する属性です。 - スクリーンリーダー: 視覚障害者向けの画面読み上げソフトウェア。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/junksidetm/darkside-toolkit/tree/HEAD/skills/frontend-a11y

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

フロントエンドアクセシビリティパターン集 は何をしますか?

React と Next.js のための実践的なアクセシビリティパターン集。コードレビューで最も頻繁に指摘される問題(フォームラベルの欠如、誤ったARIA使用法、非セマンティックなインタラクティブ要素、壊れたキーボードナビゲーション)をカバーします。

使用するタイミング

  • フォームコンポーネント(<input>、<select>、<textarea>)を構築・レビューするとき
  • インタラクティブ要素(モーダル、ドロップダウン、ツールチップ、タブ)を作成するとき
  • onClick を持つ <div> または <span> を使用するとき
  • 任意の要素に aria-* 属性を追加するとき
  • キーボードナビゲーションまたはフォーカス管理を実装するとき
  • コードレビューツール(CodeRabbit、ESLint a11y)からアクセシビリティフィードバックを受け取ったとき
  • スクリーンリーダーをサポートするコンポーネントを構築するとき

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

コードレビューで最もよく指摘される問題は、htmlFor / id のペアリングの欠如と、エラーメッセージが接続されていないことです。

ラベルの接続

// 悪い例: ラベルが入力要素に接続されていない — スクリーンリーダーはそれらを関連付けられない
<label>Email</label>
<input type="email" />

// 良い例: htmlFor が input の id と一致している
<label htmlFor="email">Email</label>
<input id="email" type="email" />

必須フィールド

// 悪い例: 視覚的なアスタリスクのみ — スクリーンリーダーには伝わらない
<label htmlFor="email">Email *</label>
<input id="email" type="email" />

// 良い例: required でブラウザのネイティブ検証を有効化、aria-required でスクリーンリーダーに通知
<label htmlFor="email">
  Email <span aria-hidden="true">*</span>
</label>
<input id="email" type="email" required aria-required="true" />

エラーメッセージ

// 悪い例: エラーテキストは視覚的に存在するが、入力要素にリンクされていない
<input id="email" type="email" />
<span className="error">無効なメールアドレスです</span>

// 良い例: aria-describedby で入力要素をエラーメッセージに接続
// aria-invalid でスクリーンリーダーに無効状態を通知
<input
  id="email"
  type="email"
  aria-describedby="email-error"
  aria-invalid={!!error}
/>
{error && (
  <span id="email-error" role="alert">
    {error}
  </span>
)}

完全なアクセシブルフォーム

interface LoginFormProps {
  onSubmit: (email: string, password: string) => void;
}

export function LoginForm({ onSubmit }: LoginFormProps) {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [errors, setErrors] = useState<{ email?: string; password?: string }>({});

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const newErrors: typeof errors = {};
    if (!email) newErrors.email = 'メールアドレスは必須です';
    if (!password) newErrors.password = 'パスワードは必須です';
    if (Object.keys(newErrors).length) {
      setErrors(newErrors);
      return;
    }
    onSubmit(email, password);
  };

  return (
    <form onSubmit={handleSubmit} noValidate>
      <div>
        <label htmlFor="email">
          メールアドレス <span aria-hidden="true">*</span>
        </label>
        <input
          id="email"
          type="email"
          value={email}
          onChange={e => setEmail(e.target.value)}
          aria-required="true"
          aria-describedby={errors.email ? 'email-error' : undefined}
          aria-invalid={!!errors.email}
          autoComplete="email"
        />
        {errors.email && (
          <span id="email-error" role="alert">
            {errors.email}
          </span>
        )}
      </div>

      <div>
        <label htmlFor="password">
          パスワード <span aria-hidden="true">*</span>
        </label>
        <input
          id="password"
          type="password"
          value={password}
          onChange={e => setPassword(e.target.value)}
          aria-required="true"
          aria-describedby={errors.password ? 'password-error' : undefined}
          aria-invalid={!!errors.password}
          autoComplete="current-password"
        />
        {errors.password && (
          <span id="password-error" role="alert">
            {errors.password}
          </span>
        )}
      </div>

      <button type="submit">ログイン</button>
    </form>
  );
}

セマンティックHTML

意図に合った要素を使用してください。スクリーンリーダーとキーボードユーザーはネイティブのセマンティクスに依存しています。

// 悪い例: div にはロール、キーボードサポート、アクセシブル名がない
<div onClick={handleClick}>送信</div>

// 良い例: button はフォーカス可能、Enter/Space でアクティブ化、

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

関連スキル