Aller au contenu principal

Accessibilité Web (a11y) dans Cursor : WCAG, ARIA et axe-core

Accessibilité Web dans Cursor

L'accessibilité web (a11y) garantit que tout le monde, y compris les personnes en situation de handicap, peut percevoir, comprendre et interagir avec votre application. Cursor, un éditeur IDE propulsé par l'IA, accélère le développement accessible en générant un balisage conforme ARIA, en exécutant des audits automatisés et en appliquant des correctifs WCAG dans toute votre base de code. Ce guide couvre les labels ARIA, la conformité WCAG, les tests axe-core, les corrections a11y assistées par l'IA et les tests avec lecteur d'écran.

Prérequis​

Avant de commencer, assurez-vous de disposer des éléments suivants :

  • Cursor (dernière version recommandée) comme éditeur IDE
  • Un projet web (React, Vue, ou HTML et JavaScript simple)
  • Node.js 18 ou supérieur pour les outils de test
  • Connaissance de base de la sémantique HTML
astuce

L'accessibilité est bien moins coûteuse à intégrer dès le départ qu'à rénover ultérieurement. Ouvrez votre projet dans Cursor dès le départ et laissez l'IA auditer les composants au fur et à mesure que vous les écrivez. Corriger un label manquant pendant le développement prend quelques secondes ; corriger le même problème dans une application en production prend des semaines.

Comprendre la conformité WCAG​

Les Web Content Accessibility Guidelines (WCAG) définissent comment rendre le contenu web accessible. Elles sont organisées autour de quatre principes, souvent appelés POUR :

  • Perceptible : les informations et les composants de l'interface utilisateur doivent être présentés de manière perceptible par les utilisateurs.
  • Utilisable : les composants de l'interface utilisateur et la navigation doivent être utilisables.
  • Compréhensible : le contenu et le fonctionnement de l'interface utilisateur doivent être compréhensibles.
  • Robuste : le contenu doit être suffisamment robuste pour être interprété par les technologies d'assistance.

Le WCAG définit trois niveaux de conformité : A (minimum), AA (l'objectif légal pour la plupart des organisations) et AAA (amélioré). Demandez à Cursor de résumer les exigences de niveau AA pour une fonctionnalité spécifique :

Listez les critères de succès WCAG 2.2 Niveau AA qui s'appliquent
à un formulaire de paiement multi-étapes avec des sélecteurs de
date et des champs de carte bancaire.

Cursor renvoie les critères pertinents et explique comment satisfaire chacun avec des suggestions de code concrètes.

Fondations du HTML sémantique​

Le HTML sémantique est la fondation de l'accessibilité. Le bon élément supprime souvent le besoin d'ARIA. Cursor vous aide à choisir et générer le balisage correct.

Génération de composants sémantiques​

Demandez à Cursor de créer une barre de navigation avec l'accessibilité intégrée :

Créez une navigation de site responsive en React qui :
- Utilise un landmark <nav> avec un nom accessible
- Possède un lien d'évitement en haut de la page
- Bascule un menu mobile avec aria-expanded et aria-controls
- Se ferme sur Échap et renvoie le focus au bouton de bascule
- Prend en charge la navigation complète au clavier

Cursor génère le composant :

import { useState, useRef, useEffect } from 'react';

export function SiteNav({ links }: { links: { href: string; label: string }[] }) {
const [open, setOpen] = useState(false);
const toggleRef = useRef<HTMLButtonElement>(null);

useEffect(() => {
if (!open) return;
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
setOpen(false);
toggleRef.current?.focus();
}
};
document.addEventListener('keydown', onKey);
return () => document.removeEventListener('keydown', onKey);
}, [open]);

return (
<>
<a href="#main" className="skip-link">Aller au contenu principal</a>
<nav aria-label="Navigation principale">
<button
ref={toggleRef}
aria-expanded={open}
aria-controls="mobile-menu"
onClick={() => setOpen((v) => !v)}
>
Menu
</button>
<ul id="mobile-menu" hidden={!open}>
{links.map((link) => (
<li key={link.href}>
<a href={link.href}>{link.label}</a>
</li>
))}
</ul>
</nav>
</>
);
}
astuce

La première règle d'ARIA est : n'utilisez pas ARIA si un élément HTML natif existe. Un véritable <button> vous offre gratuitement le focus clavier, l'activation avec Entrée et Espace, et la sémantique pour lecteur d'écran. Demandez à Cursor : « Remplace ce bouton basé sur un div par un bouton sémantique », chaque fois que vous voyez onClick sur un élément non interactif.

Labels et rôles ARIA​

Lorsque le HTML natif ne peut pas exprimer la sémantique dont vous avez besoin, ARIA comble le vide. Cursor génère des attributs ARIA corrects et avertit des erreurs courantes.

Formulaires accessibles​

Les champs de formulaire ont besoin de labels. Demandez à Cursor de générer un champ de formulaire accessible :

Créez un composant FormField réutilisable avec :
- Un <label> associé utilisant htmlFor
- Un attribut aria-required lorsque requis
- Un lien aria-describedby vers le texte d'aide et d'erreur
- Une messagerie d'erreur avec role="alert"
import { useId } from 'react';

interface FormFieldProps {
label: string;
name: string;
type?: string;
required?: boolean;
error?: string;
helper?: string;
}

export function FormField({ label, name, type = 'text', required, error, helper }: FormFieldProps) {
const fieldId = useId();
const helperId = `${fieldId}-helper`;
const errorId = `${fieldId}-error`;
const describedBy = [helper && helperId, error && errorId].filter(Boolean).join(' ') || undefined;

return (
<div>
<label htmlFor={fieldId}>
{label}
{required && <span aria-hidden="true">*</span>}
</label>
<input
id={fieldId}
name={name}
type={type}
required={required}
aria-required={required}
aria-describedby={describedBy}
aria-invalid={!!error}
/>
{helper && !error && (
<p id={helperId} className="helper-text">{helper}</p>
)}
{error && (
<p id={errorId} role="alert" className="error-text">{error}</p>
)}
</div>
);
}

Régions dynamiques pour les mises à jour​

Lorsque le contenu se met à jour dynamiquement (notifications, résultats de recherche), les lecteurs d'écran ont besoin d'une région dynamique pour annoncer les changements. Cursor génère le modèle :

export function SearchResults({ query, results }: { query: string; results: string[] }) {
return (
<div
aria-live="polite"
aria-atomic="true"
role="status"
>
{results.length === 0 ? (
<p>Aucun résultat pour « {query} ».</p>
) : (
<p>{results.length} résultats trouvés pour « {query} ».</p>
)}
<ul>
{results.map((r) => <li key={r}>{r}</li>)}
</ul>
</div>
);
}
attention

Utilisez aria-live="polite" pour les mises à jour non urgentes et aria-live="assertive" uniquement pour les alertes critiques. L'abus de régions dynamiques assertives interrompt constamment les utilisateurs de lecteur d'écran et rend votre application inutilisable. N'utilisez jamais role="alert" pour les messages de succès ; réservez-le aux véritables erreurs nécessitant une attention immédiate.

Tests automatisés avec axe-core​

Les outils automatisés détectent environ 30 à 40 pour cent des problèmes d'accessibilité. axe-core est le standard de l'industrie. Cursor peut l'intégrer dans votre suite de tests et votre pipeline CI.

Configuration de axe-core avec Jest​

Demandez à Cursor d'ajouter des tests d'accessibilité automatisés :

Ajoutez axe-core à mon projet React. Créez un test Jest qui
affiche le composant SiteNav avec Testing Library et exécute
les règles axe.

Cursor installe les dépendances et écrit le test :

npm install -D @axe-core/react axe-core jest-axe @testing-library/react @testing-library/jest-dom
// src/components/SiteNav.test.tsx
import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
import { SiteNav } from './SiteNav';

expect.extend(toHaveNoViolations);

const links = [
{ href: '/', label: 'Accueil' },
{ href: '/about', label: 'À propos' },
];

describe('SiteNav', () => {
it('n\'a aucune violation d\'accessibilité fermé', async () => {
const { container } = render(<SiteNav links={links} />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});

it('n\'a aucune violation d\'accessibilité ouvert', async () => {
const { container, getByText } = render(<SiteNav links={links} />);
getByText('Menu').click();
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});

Assistant de test axe réutilisable​

Cursor peut générer un assistant réutilisable pour que chaque test de composant reste cohérent :

import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';

expect.extend(toHaveNoViolations);

export async function expectAccessible(component: React.ReactElement) {
const { container } = render(component);
const results = await axe(container, {
rules: {
// Désactivez les règles que vous gérez intentionnellement ailleurs
region: { enabled: false },
},
});
expect(results).toHaveNoViolations();
return results;
}

Utilisez-le dans chaque test de composant :

import { expectAccessible } from '../test/expectAccessible';
import { FormField } from './FormField';

it('est accessible', async () => {
await expectAccessible(<FormField label="Email" name="email" required />);
});
astuce

Exécutez la suite axe complète avant chaque commit avec un script npm. Demandez à Cursor : « Ajoute un script a11y à package.json qui exécute tous les tests axe et échoue en cas de violation. » Cela empêche les régressions d'atteindre votre branche principale.

Corrections d'accessibilité assistées par l'IA​

L'IA de Cursor peut scanner un fichier de composant entier, trouver des problèmes d'accessibilité et appliquer des corrections en une seule passe. C'est l'un des flux de travail a11y les plus précieux.

Audit d'un composant​

Ouvrez un composant dans Cursor et exécutez ce prompt dans le chat IA :

Auditez ce composant pour la conformité WCAG 2.2 AA. Listez chaque
problème avec le numéro de ligne, expliquez pourquoi il échoue et
réécrivez le code avec les corrections. Ajoutez des commentaires
expliquant chaque changement.

Cursor peut signaler :

  • Ligne 12 : <div onClick> n'est pas accessible au clavier. Remplacez par un <button>.
  • Ligne 20 : <img> n'a pas d'attribut alt. Ajoutez un texte alternatif descriptif.
  • Ligne 28 : le contraste de couleur est de 3.1:1, inférieur au minimum de 4.5:1 pour le texte normal.
  • Ligne 35 : le dialogue modal n'a pas de piège de focus. Les utilisateurs peuvent faire Tab derrière le dialogue.

Cursor applique ensuite les corrections :

// Avant
<div className="card" onClick={openDetails}>
<img src={product.image} />
<h3 style={{ color: '#999' }}>{product.name}</h3>
</div>

// Après
<button
className="card"
onClick={openDetails}
aria-label={`Voir les détails de ${product.name}`}
>
<img src={product.image} alt={`Photo du produit ${product.name}`} />
<h3 style={{ color: '#595959' }}>{product.name}</h3>
</button>

Génération d'un piège de focus​

Les modales et les dialogues ont besoin d'un piège de focus pour que les utilisateurs au clavier ne puissent pas s'échapper du dialogue en faisant Tab. Demandez à Cursor :

Ajoutez un piège de focus à ce composant modal. Déplacez le focus
vers le premier élément focusable à l'ouverture, piègez Tab et
Shift+Tab dans le dialogue, et renvoyez le focus au bouton
déclencheur à la fermeture.
import { useEffect, useRef } from 'react';

const FOCUSABLE = 'a[href], button:not([disabled]), input, textarea, select, [tabindex]:not([tabindex="-1"])';

export function useFocusTrap(active: boolean) {
const containerRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (!active || !containerRef.current) return;
const container = containerRef.current;
const elements = container.querySelectorAll<HTMLElement>(FOCUSABLE);
if (elements.length === 0) return;

const first = elements[0];
const last = elements[elements.length - 1];
first.focus();

const onKey = (e: KeyboardEvent) => {
if (e.key !== 'Tab') return;
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
};

container.addEventListener('keydown', onKey);
return () => container.removeEventListener('keydown', onKey);
}, [active]);

return containerRef;
}
attention

Un piège de focus est inutile si le HTML sous-jacent n'est pas accessible au clavier. Le piège ne gère que les éléments réellement focusables. Si votre dialogue ne contient que des éléments <div> avec des gestionnaires onClick, le piège de focus n'a rien à cibler. Associez toujours le piège à des éléments sémantiques et focusables.

Tests avec lecteur d'écran​

Les tests automatisés détectent les problèmes structurels, mais seuls les tests manuels avec un lecteur d'écran révèlent l'expérience vécue. Cursor peut générer des scripts de test et des checklists.

Checklist de test au clavier uniquement​

Demandez à Cursor de générer une checklist de test au clavier :

Créez une checklist de test au clavier uniquement pour une application
web couvrant la navigation, les formulaires, les widgets et le
contenu dynamique.

Cursor produit :

  1. Parcourez la page avec Tab dans un ordre logique sans piège.
  2. Le lien d'évitement est le premier élément focusable et saute vers le contenu principal.
  3. Tous les éléments interactifs sont accessibles avec Tab et Shift+Tab.
  4. Les widgets personnalisés (onglets, accordéons) prennent en charge les flèches et Début/Fin.
  5. Les modales piègent le focus et le renvoient au déclencheur à la fermeture.
  6. Un indicateur de focus visible est présent sur chaque élément focusable.
  7. Entrée et Espace activent les boutons ; Entrée active les liens.
  8. Les formulaires se soumettent avec Entrée sur le bouton de soumission.

Tests avec NVDA, JAWS et VoiceOver​

Cursor peut générer des notes de test pour chaque lecteur d'écran :

Rédigez un guide de démarrage rapide pour tester mon application
avec NVDA sur Windows, VoiceOver sur macOS et TalkBack sur Android.
Incluez le raccourci pour démarrer chaque lecteur et comment
naviguer par titre et par landmark.

Cursor renvoie un tableau comparatif et une antisèche de navigation que vous pouvez enregistrer comme fichier de référence dans votre projet.

Contraste de couleur et conception visuelle​

L'accessibilité visuelle fait partie du tableau WCAG. Le faible contraste est l'une des défaillances les plus courantes.

Vérification du contraste avec l'IA​

Demandez à Cursor d'évaluer vos jetons de conception :

Voici mes jetons de couleur dans tailwind.config.js. Vérifiez chaque
paire texte et fond par rapport aux ratios de contraste WCAG AA
(4.5:1 pour le texte normal, 3:1 pour le texte large). Signalez les
défaillances et suggérez des corrections.

Cursor lit la configuration, calcule les ratios de contraste et signale les défaillances :

// Jetons tailwind.config.js qui échouent AA
// text-gray-400 sur bg-white : 2.85:1 — ÉCHEC
// Correction : utiliser text-gray-600 (#4B5563) pour 7.21:1

Cursor met à jour la configuration :

module.exports = {
theme: {
extend: {
colors: {
'text-secondary': '#4B5563', // était #9CA3AF, maintenant conforme AA
},
},
},
};
astuce

Ne comptez jamais sur la couleur seule pour transmettre une information. Une bordure rouge autour d'un champ invalide est invisible pour les utilisateurs daltoniens qui dépendent également de lecteurs d'écran. Associez toujours la couleur à du texte ou à une icône. Demandez à Cursor : « Ajoute du texte et des icônes pour transmettre les états d'erreur aux côtés de la couleur », pour appliquer la correction dans tout un formulaire.

Accessibilité en CI​

Les vérifications a11y automatisées appartiennent à votre pipeline d'intégration continue. Cursor peut générer la configuration.

Lighthouse CI avec catégorie a11y​

Demandez à Cursor d'ajouter Lighthouse CI :

Ajoutez Lighthouse CI à mon workflow GitHub Actions avec la
catégorie accessibilité activée et un score minimum de 0.9.
# .github/workflows/lighthouse.yml
name: Lighthouse
on: [pull_request]

jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npm run build
- name: Run Lighthouse
uses: treosh/lighthouse-ci-action@v11
with:
urls: |
http://localhost:3000
uploadArtifacts: true
configPath: .lighthouserc.json
// .lighthouserc.json
{
"ci": {
"assert": {
"assertions": {
"categories:accessibility": ["error", { "minScore": 0.9 }]
}
},
"collect": {
"startServerCommand": "npm run start",
"startServerReadyPattern": "ready",
"numberOfRuns": 3
}
}
}

Résumé des bonnes pratiques​

Gardez ces principes à l'esprit lors de la création d'applications accessibles dans Cursor :

  • Privilégiez le HTML sémantique plutôt qu'ARIA chaque fois qu'un élément natif convient.
  • Étiquetez chaque champ avec un véritable <label> ou un aria-label.
  • Rendez tout utilisable au clavier ; la souris est optionnelle.
  • Automatisez avec axe-core dans chaque test de composant.
  • Testez manuellement avec au moins un lecteur d'écran.
  • Maintenez un contraste de 4.5:1 pour le texte normal et des indicateurs de focus visibles.
  • Exécutez des vérifications a11y en CI pour que les régressions ne soient jamais livrées.

En combinant la connaissance du WCAG avec l'audit et la génération de code assistés par l'IA de Cursor, vous pouvez créer des interfaces inclusives qui fonctionnent pour tout le monde. Laissez Cursor générer le balisage et les tests accessibles, puis vérifiez les résultats avec de véritables lecteurs d'écran avant de livrer.