Communitygithub.com

frontend-a11y

Modèles d'accessibilité pour React et Next.js, couvrant les problèmes courants de revue de code : libellés de formulaire, utilisation des attributs ARIA, éléments interactifs sémantiques et navigation au clavier.

Qu'est-ce que frontend-a11y ?

Ce skill fournit des modèles d'accessibilité éprouvés pour les développeurs React et Next.js, en se concentrant sur les problèmes les plus souvent signalés lors des revues de code. Il couvre la connexion correcte des libellés de formulaire (htmlFor/id), l'utilisation appropriée des attributs ARIA (aria-label, aria-labelledby, aria-describedby, aria-live, aria-expanded/aria-controls), le remplacement des éléments non sémantiques (div, span avec onClick) par des balises HTML natives, et l'implémentation de la navigation au clavier (flèches, Tab, Enter/Espace, Échap) et de la gestion du focus (modale, restauration du focus). Il inclut également des sections sur les images et icônes accessibles, le respect de la réduction de mouvement (prefers-reduced-motion), une checklist avant validation, et des anti-patrons à éviter. Le skill est activé lors de la construction ou revue de formulaires, d'éléments interactifs, de composants nécessitant des attributs ARIA ou une navigation au clavier, et pour répondre aux retours d'outils comme CodeRabbit ou ESLint a11y.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/frontend-a11y

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Aperçu

Depuis le README du skill

frontend-a11y preview
frontend-a11y preview
frontend-a11y preview

Documentation

Modèles d'accessibilité front-end

Modèles d'accessibilité pratiques pour React et Next.js. Couvre les problèmes les plus souvent signalés lors des revues de code : libellés de formulaire manquants, utilisation incorrecte d'ARIA, éléments interactifs non sémantiques et navigation au clavier cassée.

Quand activer

  • Lors de la construction ou de la revue de composants de formulaire (<input>, <select>, <textarea>)
  • Lors de la création d'éléments interactifs (modales, menus déroulants, infobulles, onglets)
  • Lors de l'utilisation de <div> ou <span> avec onClick
  • Lors de l'ajout d'attributs aria-* à un élément
  • Lors de l'implémentation de la navigation au clavier ou de la gestion du focus
  • Lors de la réception de retours d'accessibilité d'outils de revue de code (CodeRabbit, ESLint a11y)
  • Lors de la construction de composants devant prendre en charge les lecteurs d'écran

Accessibilité des formulaires

L'absence de couplage htmlFor / id et les messages d'erreur déconnectés sont les problèmes les plus souvent signalés lors des revues de code.

Connexion du libellé

// MAUVAIS : le libellé n'a aucune connexion avec l'entrée — les lecteurs d'écran ne peuvent pas les associer
<label>Email</label>
<input type="email" />

// BON : htmlFor correspond à l'id de l'entrée
<label htmlFor="email">Email</label>
<input id="email" type="email" />

Champs obligatoires

// MAUVAIS : l'astérisque visuel ne transmet rien aux lecteurs d'écran
<label htmlFor="email">Email *</label>
<input id="email" type="email" />

// BON : required active la validation native du navigateur ; aria-required le signale aux lecteurs d'écran
<label htmlFor="email">
  Email <span aria-hidden="true">*</span>
</label>
<input id="email" type="email" required aria-required="true" />

Messages d'erreur

// MAUVAIS : le texte d'erreur existe visuellement mais n'est pas lié à l'entrée
<input id="email" type="email" />
<span className="error">Adresse email invalide</span>

// BON : aria-describedby connecte l'entrée à son message d'erreur
// aria-invalid signale l'état invalide aux lecteurs d'écran
<input
  id="email"
  type="email"
  aria-describedby="email-error"
  aria-invalid={!!error}
/>
{error && (
  <span id="email-error" role="alert">
    {error}
  </span>
)}

Formulaire accessible complet

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 = 'L\'email est obligatoire';
    if (!password) newErrors.password = 'Le mot de passe est obligatoire';
    if (Object.keys(newErrors).length) {
      setErrors(newErrors);
      return;
    }
    onSubmit(email, password);
  };

  return (
    <form onSubmit={handleSubmit} noValidate>
      <div>
        <label htmlFor="email">
          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">
          Mot de passe <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">Se connecter</button>
    </form>
  );
}

HTML sémantique

Utilisez l'élément qui correspond à l'intention. Les lecteurs d'écran et les utilisateurs du clavier dépendent de la sémantique native.

// MAUVAIS : div n'a pas de rôle, pas de support clavier, pas de nom accessible
<div onClick={handleClick}>Soumettre</div>

// BON : button est focusable, s'active avec Entrée/Espace, s'annonce comme "bouton"
<button type="button" onClick={handleClick}>Soumettre</button>
// MAUVAIS : navigation non sémantique
<div onClick={() => navigate('/home')}>Accueil</div>

// BON : ancre prend en charge le clic droit, le clic milieu et la navigation au clavier
<a href="/home">Accueil</a>
// MAUVAIS : hiérarchie des titres sautée (h1 à h4)
<h1>Tableau de bord</h1>
<h4>Activité récente</h4>

// BON : niveaux de titres séquentiels
<h1>Tableau de bord</h1>
<h2>Activité récente</h2>

Attributs ARIA

N'utilisez ARIA que lorsque la sémantique HTML native est insuffisante. Un mauvais ARIA est pire que pas d'ARIA du tout.

aria-label vs aria-labelledby

// aria-label : libellé de chaîne en ligne — à utiliser lorsqu'aucun texte de libellé visible n'existe
<button aria-label="Fermer la modale">
  <XIcon />
</button>

// aria-labelledby : référence le texte d'un autre élément — à utiliser lorsqu'un libellé visible existe
<section aria-labelledby="section-title">
  <h2 id="section-title">Commandes récentes</h2>
  {/* contenu */}
</section>

aria-describedby

// Fournit une description supplémentaire au-delà du libellé
<button
  aria-describedby="delete-warning"
  onClick={handleDelete}
> Supprimer le compte
</button>
<p id="delete-warning">Cette action ne peut pas être annulée.</p>

aria-live pour le contenu dynamique

// Utilisez aria-live pour annoncer le contenu qui se met à jour sans rechargement de page
// polite : attend que l'utilisateur termine son action en cours avant d'annoncer
// assertive : interrompt immédiatement — à utiliser uniquement pour les erreurs urgentes

export function StatusMessage({ message, isError }: { message: string; isError?: boolean }) {
  return (
    <div role="status" aria-live={isError ? 'assertive' : 'polite'} aria-atomic="true">
      {message}
    </div>
  );
}

aria-expanded et aria-controls

export function Accordion({ title, children }: { title: string; children: React.ReactNode }) {
  const [isOpen, setIsOpen] = useState(false);
  const contentId = useId();

  return (
    <div>
      <button aria-expanded={isOpen} aria-controls={contentId} onClick={() => setIsOpen(prev => !prev)}>
        {title}
      </button>
      <div id={contentId} hidden={!isOpen}>
        {children}
      </div>
    </div>
  );
}

Navigation au clavier

Chaque élément interactif doit pouvoir être atteint et utilisé uniquement avec le clavier.

Menu déroulant personnalisé

export function Dropdown({ options, onSelect }: { options: string[]; onSelect: (value: string) => void }) {
  const [isOpen, setIsOpen] = useState(false);
  const [activeIndex, setActiveIndex] = useState(0);
  const listId = useId();

  if (!options.length) return null;

  const handleKeyDown = (e: React.KeyboardEvent) => {
    switch (e.key) {
      case 'ArrowDown':
        e.preventDefault();
        setActiveIndex(i => Math.min(i + 1, options.length - 1));
        break;
      case 'ArrowUp':
        e.preventDefault();
        setActiveIndex(i => Math.max(i - 1, 0));
        break;
      case 'Enter':
      case ' ':
        e.preventDefault();
        if (isOpen) onSelect(options[activeIndex]);
        setIsOpen(prev => !prev);
        break;
      case 'Escape':
        setIsOpen(false);
        break;
    }
  };

  return (
    <div
      role="combobox"
      aria-expanded={isOpen}
      aria-haspopup="listbox"
      aria-controls={listId}
      tabIndex={0}
      onKeyDown={handleKeyDown}
      onClick={() => setIsOpen(prev => !prev)}
    >
      <span>{options[activeIndex]}</span>
      {isOpen && (
        <ul id={listId} role="listbox">
          {options.map((option, index) => (
            <li
              key={option}
              role="option"
              aria-selected={index === activeIndex}
              onClick={() => {
                onSelect(option);
                setIsOpen(false);
              }}
            >
              {option}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

Gestion du focus

Le focus doit se déplacer logiquement lorsque l'état de l'interface change, en particulier pour les modales et les transitions de route.

Restauration du focus de la modale

Cet exemple couvre le focus initial et la restauration. Pour un piège à focus complet (cycle Tab/Maj+Tab dans la modale), utilisez une bibliothèque comme focus-trap-react qui gère les cas particuliers comme le contenu dynamique et les portails imbriqués.

export function Modal({ isOpen, onClose, title, children }: { isOpen: boolean; onClose: () => void; title: string; children: React.ReactNode }) {
  const modalRef = useRef<HTMLDivElement>(null);
  const previousFocusRef = useRef<HTMLElement | null>(null);

  useEffect(() => {
    if (isOpen) {
      // Sauvegarde l'élément actuellement focusé et déplace le focus dans la modale
      previousFocusRef.current = document.activeElement as HTMLElement;
      modalRef.current?.focus();
    } else {
      // Restaure le focus sur l'élément qui a ouvert la modale
      previousFocusRef.current?.focus();
    }
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    <div ref={modalRef} role="dialog" aria-modal="true" aria-labelledby="modal-title" tabIndex={-1} onKeyDown={e => e.key === 'Escape' && onClose()}>
      <h2 id="modal-title">{title}</h2>
      {children}
      <button onClick={onClose}>Fermer</button>
    </div>
  );
}

Images et icônes

// MAUVAIS : icône décorative annoncée comme image sans libellé
<img src="/icon.svg" />

// BON : image décorative masquée des lecteurs d'écran
<img src="/decoration.png" alt="" aria-hidden="true" />

// BON : image significative avec texte alternatif descriptif
<img src="/chart.png" alt="Le revenu mensuel a augmenté de 23% de janvier à mars" />

// BON : bouton icône avec libellé accessible
<button aria-label="Supprimer l'élément">
  <TrashIcon aria-hidden="true" />
</button>

Réduction de mouvement

Respectez les utilisateurs qui ont demandé une réduction de mouvement dans les paramètres de leur système d'exploitation.

export function useReducedMotion(): boolean {
  const [prefersReduced, setPrefersReduced] = useState(false);

  useEffect(() => {
    const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
    setPrefersReduced(mq.matches);
    const handler = (e: MediaQueryListEvent) => setPrefersReduced(e.matches);
    mq.addEventListener('change', handler);
    return () => mq.removeEventListener('change', handler);
  }, []);

  return prefersReduced;
}

// Utilisation
export function AnimatedCard({ children }: { children: React.ReactNode }) {
  const reduceMotion = useReducedMotion();

  return (
    <div
      style={{
        transition: reduceMotion ? 'none' : 'transform 300ms ease'
      }}
    >
      {children}
    </div>
  );
}

Anti-patrons

// MAUVAIS : onClick sur un élément non interactif sans support clavier
<div onClick={handleClick}>Cliquez-moi</div>

// MAUVAIS : aria-label sur une div qui n'a pas de rôle
<div aria-label="Navigation">...</div>

// MAUVAIS : placeholder utilisé comme substitut de libellé
<input placeholder="Saisissez votre email" />

// MAUVAIS : tabIndex positif crée un ordre de tabulation imprévisible
<button tabIndex={3}>Soumettre</button>

// MAUVAIS : aria-hidden sur un élément focusable — les utilisateurs du clavier sont piégés
<button aria-hidden="true">Ouvrir</button>

// MAUVAIS : role="button" sur une div sans gestionnaire clavier
<div role="button" onClick={handleClick}>Soumettre</div>
// Manquant : tabIndex={0}, onKeyDown pour Entrée/Espace

Liste de vérification

Avant de soumettre un composant interactif à la revue :

  • Chaque <input>, <select> et <textarea> a un <label> connecté via htmlFor/id
  • Les messages d'erreur sont liés avec aria-describedby et marqués role="alert"
  • Aucun onClick sur <div> ou <span> sans role, tabIndex et onKeyDown
  • Les boutons avec uniquement une icône ont un aria-label
  • Les images décoratives utilisent alt="" et aria-hidden="true"
  • Les modales restaurent le focus à la fermeture (pour un piège à focus complet avec Tab/Maj+Tab, utilisez une bibliothèque comme focus-trap-react)
  • Les mises à jour de contenu dynamique utilisent aria-live
  • prefers-reduced-motion est respecté pour les animations

Compétences associées

  • frontend-patterns — modèles généraux de composants React et d'état
  • design-system — cohérence des jetons de design et des composants
  • motion-foundations et motion-patterns : modèles d'animation avec considérations d'accessibilité

Individual skills in this repo

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

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

Skills associés