1. Images (Thématique 1)

Critère 1.1 — Toute image porteuse d'information a une alternative textuelle (Niveau A)

L'attribut alt doit décrire l'information véhiculée par l'image, pas son apparence. Une image décorative doit avoir un alt vide (alt=""), jamais absent.

✅ Correct

<!-- Image informative -->
<img src="graphique-ventes-q3.png"
     alt="Graphique des ventes Q3 2024 : +23% vs Q2">

<!-- Image décorative -->
<img src="separateur.svg" alt="" role="presentation">

<!-- SVG inline -->
<svg role="img" aria-labelledby="svg-title">
  <title id="svg-title">Logo de l'entreprise Acme</title>
</svg>

❌ Incorrect

<img src="graphique.png">               <!-- alt absent -->
<img src="graphique.png" alt="image">   <!-- alt non descriptif -->

Règle rapide : si vous pouvez retirer l'image sans perte d'information, alt="". Sinon, décrivez l'information, pas l'apparence.


Critère 1.3 — Description détaillée pour les images complexes (Niveau A)

Pour les graphiques et schémas, une description longue doit compléter le alt court.

✅ Correct

<figure>
  <img src="schema-archi.png"
       alt="Architecture microservices"
       aria-describedby="desc-schema">
  <figcaption id="desc-schema">
    Ce schéma présente 3 microservices : Auth, API Gateway et Service Métier.
    L'Auth communique avec l'API Gateway via JWT...
  </figcaption>
</figure>

2. Couleurs (Thématique 3)

Critère 3.2 — Contraste suffisant entre texte et arrière-plan (Niveau AA)

Ratio minimum requis : 4,5:1 pour le texte courant, 3:1 pour le grand texte (≥ 18pt ou ≥ 14pt gras). Cette exigence s'applique aussi aux composants UI et aux éléments graphiques porteurs d'information.

✅ Correct

:root {
  --color-text: #1a1a2e;       /* ratio 7.1:1 sur blanc ✓ */
  --color-placeholder: #6b7280; /* ratio 4.6:1 sur blanc ✓ */
}

button:disabled {
  color: #767676;      /* 4.54:1 sur blanc ✓ */
  background: #e5e5e5;
}

❌ Incorrect

--color-text: #aaaaaa;          /* ratio 2.1:1 — insuffisant */
input::placeholder { color: #cccccc; } /* ratio 1.8:1 */

Critère 3.1 — L'information n'est pas transmise uniquement par la couleur (Niveau A)

Un marqueur visuel supplémentaire (icône, bordure, texte) doit toujours accompagner l'information colorée.

✅ Correct

<div class="field-error">
  <label for="email">Email</label>
  <input type="email" id="email"
         aria-describedby="email-error"
         aria-invalid="true">
  <p id="email-error" role="alert">
    ⚠ Adresse email invalide
  </p>
</div>
.field-error input {
  border-color: #dc2626;
  border-left-width: 4px; /* marqueur structurel en plus de la couleur */
}

❌ Incorrect

.has-error { border-color: red; } /* seul indicateur d'erreur */

3. Liens (Thématique 6)

Critère 6.1 — Chaque lien a un intitulé explicite (Niveau A)

L'intitulé doit être compréhensible seul, hors contexte. Éviter "cliquez ici", "en savoir plus", "lire la suite".

✅ Correct

<!-- Intitulé descriptif -->
<a href="/rapport-annuel-2024.pdf">
  Télécharger le rapport annuel 2024 (PDF, 2 Mo)
</a>

<!-- Complément masqué visuellement -->
<a href="/produits/chaise-oslo">
  En savoir plus
  <span class="sr-only"> sur la chaise Oslo</span>
</a>

<!-- Lien icône seul : aria-label obligatoire -->
<a href="https://github.com/mon-projet"
   aria-label="Code source sur GitHub (nouvelle fenêtre)"
   target="_blank" rel="noopener noreferrer">
  <svg aria-hidden="true">...</svg>
</a>
/* Classe utilitaire incontournable */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

Critère 6.4 — Ouverture en nouvelle fenêtre signalée (Niveau A)

✅ Correct

<a href="https://externe.fr" target="_blank" rel="noopener noreferrer">
  Documentation officielle
  <span aria-hidden="true"> ↗</span>
  <span class="sr-only">(ouvre une nouvelle fenêtre)</span>
</a>

4. Formulaires (Thématique 11)

Critère 11.1 — Chaque champ a une étiquette associée (Niveau A)

Le placeholder seul ne suffit pas. Un <label> lié via for/id est obligatoire.

✅ Correct

<!-- Association for/id -->
<label for="nom">Nom <span aria-hidden="true">*</span></label>
<input type="text" id="nom" name="nom" required aria-required="true">

<!-- Groupe radio : fieldset + legend obligatoires -->
<fieldset>
  <legend>Méthode de paiement</legend>
  <label><input type="radio" name="paiement" value="cb"> Carte bancaire</label>
  <label><input type="radio" name="paiement" value="vir"> Virement</label>
</fieldset>

❌ Incorrect

<input type="text" placeholder="Votre nom">  <!-- label absent -->
<label>Email</label><input type="email">     <!-- label non associé -->

Critère 11.10 — Erreurs de saisie accessibles (Niveau AA)

✅ Correct

<label for="pwd">Mot de passe</label>
<p id="pwd-hint">8 caractères minimum, dont 1 chiffre et 1 majuscule</p>
<input type="password" id="pwd"
       aria-describedby="pwd-hint pwd-error"
       aria-invalid="true">
<p id="pwd-error" role="alert">
  Le mot de passe doit contenir au moins 8 caractères.
</p>

<!-- Résumé d'erreurs en haut du formulaire -->
<div role="alert" aria-live="assertive">
  <h2>2 erreurs dans le formulaire</h2>
  <ul>
    <li><a href="#nom">Le nom est obligatoire</a></li>
    <li><a href="#pwd">Mot de passe invalide</a></li>
  </ul>
</div>

5. Multimédia (Thématique 4)

Critère 4.1 — Vidéo avec audio : sous-titres synchronisés (Niveau A)

✅ Correct

<video controls aria-label="Tutoriel installation Node.js">
  <source src="tuto.mp4" type="video/mp4">
  <track kind="subtitles"
         src="tuto-fr.vtt"
         srclang="fr"
         label="Français"
         default>
  <track kind="descriptions"
         src="tuto-audiodesc.vtt"
         srclang="fr"
         label="Audiodescription">
</video>
<p><a href="tuto-transcription.html">Transcription textuelle du tutoriel</a></p>

Important : les contenus audio et vidéo ne doivent pas se lancer automatiquement (critère 4.10), sauf si leur durée est ≤ 3 secondes ou qu'un mécanisme d'arrêt est immédiatement disponible.


6. Tableaux (Thématique 5)

Critères 5.1 à 5.4 — Structure et en-têtes (Niveau A)

✅ Correct

<!-- Tableau simple -->
<table>
  <caption>Répartition des tickets par statut – Juin 2024</caption>
  <thead>
    <tr>
      <th scope="col">Statut</th>
      <th scope="col">Nombre</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Ouvert</th>
      <td>42</td>
    </tr>
  </tbody>
</table>

<!-- Tableau complexe : id + headers -->
<table>
  <caption>Ventes par région et trimestre</caption>
  <tr>
    <td></td>
    <th id="q1" scope="col">T1</th>
    <th id="q2" scope="col">T2</th>
  </tr>
  <tr>
    <th id="nord" scope="row">Nord</th>
    <td headers="nord q1">12k€</td>
    <td headers="nord q2">15k€</td>
  </tr>
</table>

❌ Incorrect

<!-- Pas de th, pas de caption -->
<tr><td><strong>Statut</strong></td></tr>

<!-- Table pour la mise en page -->
<table role="presentation">...</table>  <!-- ne jamais faire -->

7. Navigation & Focus (Thématique 12)

Critère 12.8 — Focus clavier visible (Niveau AA)

Ne jamais supprimer l'outline sans alternative visible.

✅ Correct

:focus-visible {
  outline: 3px solid #2563eb;
  outline-offset: 3px;
  border-radius: 4px;
}

/* Skip link */
.skip-link {
  position: absolute;
  top: -100%;
  left: 1rem;
  background: #1e40af;
  color: #fff;
  padding: 0.75rem 1.5rem;
  border-radius: 0 0 8px 8px;
  text-decoration: none;
  font-weight: 600;
}
.skip-link:focus { top: 0; }
<a href="#contenu-principal" class="skip-link">Aller au contenu principal</a>
<main id="contenu-principal">...</main>

❌ Incorrect

* { outline: none; }          /* suppression globale interdite */
*:focus { outline: 0; }

Critère 12.6 — Landmarks ARIA (Niveau A)

✅ Correct

<body>
  <a href="#main" class="skip-link">Aller au contenu</a>

  <header role="banner">
    <nav aria-label="Navigation principale">...</nav>
  </header>

  <main id="main" role="main">
    <h1>Titre de la page</h1>
    <aside aria-label="Articles en relation">...</aside>
  </main>

  <footer role="contentinfo">
    <nav aria-label="Navigation secondaire">...</nav>
  </footer>
</body>

8. Scripts & Composants dynamiques (Thématique 7)

Critère 7.1 — Rôles, états et propriétés ARIA (Niveau A)

✅ Correct — Modale

<button type="button" aria-haspopup="dialog" onclick="openModal()">
  Ouvrir les paramètres
</button>

<div id="modal" role="dialog" aria-modal="true"
     aria-labelledby="modal-title" aria-hidden="true">
  <h2 id="modal-title">Paramètres</h2>
  <button onclick="closeModal()" aria-label="Fermer les paramètres">×</button>
</div>
function openModal() {
  const modal = document.getElementById('modal');
  modal.setAttribute('aria-hidden', 'false');
  modal.removeAttribute('hidden');
  modal.querySelector('button, input, [tabindex]')?.focus();
}

function closeModal() {
  const modal = document.getElementById('modal');
  modal.setAttribute('aria-hidden', 'true');
  document.querySelector('[aria-haspopup="dialog"]')?.focus();
}

✅ Correct — Menu déroulant

<button type="button"
        aria-expanded="false"
        aria-controls="submenu"
        aria-haspopup="true">
  Produits
</button>

<ul id="submenu" role="menu" hidden>
  <li role="none"><a href="/logiciels" role="menuitem">Logiciels</a></li>
  <li role="none"><a href="/services" role="menuitem">Services</a></li>
</ul>
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') closeMenu();
});

Règle d'or : tout élément non-natif (div, span) cliquable doit avoir un role, être focusable (tabindex="0") et répondre au clavier selon les patterns ARIA Authoring Practices.


9. Structure de la page (Thématique 9)

Critère 9.1 — Hiérarchie des titres sans saut (Niveau A)

✅ Correct

<h1>Catalogue produits</h1>   <!-- unique par page -->
  <h2>Logiciels</h2>
    <h3>Licence individuelle</h3>
    <h3>Licence entreprise</h3>
  <h2>Services</h2>

❌ Incorrect

<h1>Catalogue</h1>
  <h3>Logiciels</h3>   <!-- saut de niveau interdit -->

<h1>Titre A</h1>
<h1>Titre B</h1>        <!-- deux h1 interdits -->

Ne choisissez jamais un niveau de titre pour son rendu visuel. Stylez avec CSS, pas avec le niveau de balisage.


Checklist rapide développeur

ThématiquePoint de contrôleNiveau
ImagesChaque <img> a un alt (vide si décorative)A
ImagesSVG inline : role="img" + <title>A
CouleursRatio de contraste texte ≥ 4,5:1AA
CouleursInformation non transmise uniquement par la couleurA
LiensIntitulés explicites, compréhensibles hors contexteA
Lienstarget="_blank" signalés dans l'intituléA
FormulairesChaque champ a un <label> associéA
FormulairesChamps en erreur : aria-invalid + message descriptifAA
FormulairesGroupes radio/checkbox : <fieldset> + <legend>A
NavigationFocus visible sur tous les éléments interactifsAA
NavigationLien d'évitement "Aller au contenu" présentA
NavigationLandmarks ARIA (main, nav, header, footer)A
Tableaux<th scope> et <caption> sur les tableaux de donnéesA
TableauxAucune <table> utilisée pour la mise en pageA
ScriptsModales/menus : aria-expanded, aria-haspopup, aria-modalA
ScriptsNavigation clavier complète (Tab, Entrée, Espace, Échap)A
StructureHiérarchie h1–h6 sans saut, un seul h1 par pageA
MultimédiaVidéos : sous-titres + transcription textuelleA
MultimédiaPas d'autoplay sans mécanisme d'arrêt immédiatAA

Outils recommandés

  • axe DevTools — Extension navigateur + intégration CI/CD via axe-core
  • WAVE — Évaluation visuelle en page (extension Chrome/Firefox)
  • Colour Contrast Analyser — Outil desktop TPGi pour vérifier les ratios
  • NVDA / VoiceOver — Test avec lecteur d'écran (Windows / macOS)
  • Tanaguru web ext — Extension navigateur

Documentation basée sur le RGAA 4.1.2 — publié par la DINUM. Référence officielle : accessibilite.numerique.gouv.fr. Le RGAA 5 est attendu pour fin 2026.