RGAA Lab

Composants accessibles

La pagination

Une pagination est une liste de liens dans une zone de navigation nommée. Tout se joue sur ce que « 3 » annonce quand on le sort de la page. Démo, code, critères RGAA.

5 critères RGAA en jeu

Essayez-la

Résultats 37 à 48 sur 78

Tabulez dans la pagination : chaque lien est un arrêt, et rien d’autre n’est à apprendre — c’est un composant sans clavier propre. Puis actionnez le bouton d’inspection : il lit le DOM réel et affiche le nom accessible de chaque lien, tel qu’un lecteur d’écran le donne dans sa liste de liens — chevrons décoratifs exclus, comme il les exclut. Enfin, depuis la deuxième page, activez « Page précédente » à la touche Entrée : le lien s’efface, et le focus se repose sur le lien de la page devenue courante plutôt que de tomber dans le vide.

Cette démo existe aussi en page autonome.

L’erreur courante

Une rangée de chiffres, la page courante peinte en bleu, deux chevrons aux extrémités.

<!-- À ne pas faire -->
<div class="pagination">
	<a href="?page=3" class="fleche"></a>
	<a href="?page=1">1</a>
	<a href="?page=2">2</a>
	<span class="actif">3</span>
	<a href="?page=4">4</a>
	<a href="?page=5">5</a>
	<a href="?page=5" class="fleche"></a>
</div>
/* À ne pas faire : l'état ne tient qu'à un fond coloré */
.actif {
	background: #1d4ed8;
	color: #fff;
}

Passez cette pagination dans la liste de liens d’un lecteur d’écran, et voici ce qu’il en reste : « ‹ », « 1 », « 2 », « 4 », « 5 », « › ». Six liens, aucune destination.

On croit souvent qu’un aria-label="Pagination" sur le conteneur sauve les intitulés numérotés. Il ne les sauve pas. Le contexte du lien que le RGAA accepte est limitatif : la phrase, le paragraphe, l’item de liste, le titre qui précède, la cellule d’en-tête ou de données. Le nom d’une région n’y est pas. Un lien dont l’intitulé est « 3 » reste non explicite, quel que soit le nom du <nav> qui l’entoure.

Trois variantes secondaires, dans la même page :

  • Le chevron nu. <a href="?page=4">›</a> n’a pour intitulé qu’un caractère typographique ; en SVG sans alternative, il n’en a aucun, et le lecteur d’écran lit l’URL. On entend alors « point d’interrogation page égale quatre ».
  • La page courante en <span>. Elle sort de la liste de liens, et le lecteur d’écran passe de « 2 » à « 4 » sans rien annoncer entre les deux : le rang courant devient introuvable au clavier comme à la voix.
  • Le lien « précédent » grisé sur la première page. Grisé n’est pas désactivé : il garde le focus, s’active, et mène à une page qui n’existe pas.

Le code, pas à pas

1. Une zone de navigation, nommée

<nav aria-label="Pagination des résultats"></nav>

Le <nav> n’est pas décoratif : le critère 9.2 demande que les zones de navigation principales et secondaires soient structurées ainsi, et une pagination en est une. Le glossaire du référentiel le dit d’ailleurs en toutes lettres, en rangeant « une liste de navigation d’une liste de résultats » parmi les barres de navigation.

L’aria-label distingue ce <nav> des autres. Une page en porte souvent trois — navigation principale, fil d’Ariane, pagination — et sans nom, la liste des régions d’un lecteur d’écran affiche trois fois « navigation ». Le référentiel n’impose pas ce nom ; l’utilisabilité, oui.

Une réserve : ne nommez pas la région « Navigation de pagination ». Le rôle est déjà annoncé, et l’on entendrait « navigation de pagination, navigation ».

2. Une liste, parce que c’en est une

<nav aria-label="Pagination des résultats">
	<ol>
		<li><a href="?page=1">Page 1</a></li>
		<li><a href="?page=2">Page 2</a></li>
	</ol>
</nav>

Des numéros alignés sont « des informations regroupées visuellement sous forme de liste » au sens du critère 9.3 : le balisage de liste est attendu. L’<ol> plutôt que l’<ul>, puisque l’ordre porte du sens.

Le gain est concret : le lecteur d’écran annonce « liste de 7 éléments » et le rang de chacun. C’est ce qui remplace le repère visuel « je suis au milieu ».

Comme les puces sont retirées en CSS, ajoutez role="list" sur l’<ol>. Ce n’est pas une exigence du référentiel — un <ol> sans puces reste correctement balisé au sens du 9.3 — mais certaines versions de Safari avec VoiceOver retirent le rôle de liste quand list-style: none est appliqué, et le rôle explicite le rend.

3. Des intitulés qui se suffisent

<a href="?page=3"><span class="sr-only">Page </span>3</a>

Le nom accessible devient « Page 3 », l’affichage reste « 3 ». C’est le compromis habituel : le critère 6.1 est satisfait, la pagination garde sa largeur.

Un aria-label="Page 3" sur un lien qui affiche « 3 » fonctionne aussi, et respecte le test 6.1.5 — le nom accessible contient au moins l’intitulé visible. Le complément masqué lui reste préférable pour une raison qui n’est pas dans le référentiel : un utilisateur de commande vocale dit « cliquer sur Page 3 » et le navigateur trouve le texte, là où un aria-label seul crée parfois un décalage entre ce qui se voit et ce qui se prononce.

4. Les flèches portent un nom, le chevron est décoratif

<a href="?page=3">
	<span aria-hidden="true"></span> Page précédente
</a>

Le texte fait l’intitulé, le chevron ne fait que l’illustrer — d’où l’aria-hidden, qui évite qu’un lecteur d’écran ne prononce le caractère. Si la place manque et que la flèche doit rester seule, l’alternative est un aria-label="Page précédente" : ce qui n’est jamais acceptable, c’est un lien sans aucun intitulé, ce que le critère 6.2 relève directement.

« Précédente » et « suivante », plutôt que « Page 3 » et « Page 5 » : le lien dit ce qu’il fait, et son intitulé ne change pas d’une page à l’autre.

Sur la première page, retirez le lien « précédente » du DOM plutôt que de le griser. Un <a> n’a pas d’état désactivé : aria-disabled="true" l’annonce sans l’empêcher, et le clic partirait quand même.

5. La page courante : aria-current, et un signe visible

<li>
	<a href="?page=4" aria-current="page"><span class="sr-only">Page </span>4</a>
</li>

aria-current="page" est l’annonce : « Page 4, lien, page actuelle ». Le lien reste un lien — il permet de recharger, et surtout il reste dans la liste de liens, à sa place dans la suite des numéros.

Côté visible, le critère 3.1 tranche : l’information ne doit pas être portée par la seule couleur, et un fond bleu est exactement cela. Doublez-le d’une graisse, d’un trait épais, d’un encadrement — d’une forme, en somme. La démo emploie une graisse et une bordure basse de trois pixels.

Le référentiel nomme aria-current. Le glossaire « Information (donnée par la couleur) », rattaché au critère 3.1, range « aria-current » parmi les compléments au niveau du code qui accompagnent un moyen graphique ou un autre style typographique : « Un autre style typographique (gras, italique, taille de texte, autre police, etc) et par le biais d’un complément au niveau du code (aria-label, title, texte masqué, aria-current, etc.) ». Les deux vont ensemble : la graisse et le trait pour l’œil, aria-current="page" pour les technologies d’assistance.

6. Si la liste est longue : tronquer sans mentir

Au-delà d’une dizaine de pages, on n’affiche plus tout : 1 … 4 5 6 … 42. Les points de suspension ne sont pas un lien et ne doivent pas en devenir un. Mettez-les dans un <li> avec aria-hidden="true", ou remplacez-les par un saut explicite — un lien « Page 20 » vaut mieux qu’un « … » qu’on ne peut pas viser.

7. Une pagination qui ne recharge pas doit s’annoncer

Quand la navigation recharge la page, le changement se dit tout seul : nouveau titre de document, focus replacé en tête. Quand la liste se met à jour en place, rien ne bouge pour un lecteur d’écran — le focus reste sur le lien, et la liste a changé derrière.

Deux réponses, à combiner : une région role="status" présente dès le premier rendu qui annonce « Résultats 37 à 48 sur 78 », et le focus déplacé en tête de la liste de résultats. La région créée au moment de l’annonce n’est pas restituée.

Le focus n’est pas seulement à déplacer, il est à rattraper : le lien qu’on vient d’activer peut disparaître du DOM, puisque « précédente » n’existe plus une fois la première page atteinte. Le focus retombe alors sur body, sans rien annoncer. À défaut d’une liste de résultats où le poser — c’est le cas de la démo ci-dessus —, replacez-le sur le lien de la page devenue courante, qui survit à tous les rendus.

8. En avez-vous besoin ?

Une pagination sert à retrouver sa place et à revenir. Si vos contenus se lisent d’un flux continu, un bouton « Afficher 12 résultats de plus » est plus simple et plus sûr au clavier. Si l’on doit pouvoir citer, partager, revenir en arrière ou imprimer un rang précis, la pagination avec ses URL reste supérieure — c’est son vrai avantage sur le défilement infini, qui n’en a aucun de ce côté.

Au clavier

Une pagination n’a pas de clavier propre : ce sont des liens, et le navigateur fait tout. Il n’y a ni flèches à câbler, ni tabindex à poser. Si vous vous surprenez à écrire un keydown, c’est que le composant a dérivé.

ToucheEffet
TabPasse au lien suivant de la pagination, dans l’ordre affiché.
EntréeSuit le lien. Avec rechargement, le focus repart en tête de page ; sans rechargement, c’est à vous de le replacer — la démo le pose sur le lien de la page devenue courante.
Navigation par régionsAmène directement à la pagination, parce qu’elle est un <nav> nommé.
Liste des liensAffiche les intitulés hors contexte — le seul test qui compte ici.

Ce que le lecteur d’écran restitue

En entrant dans la région : « Pagination des résultats, navigation, liste de 7 éléments ». Sur un lien : « Page 3, lien, 3 sur 7 ». Sur la page en cours : « Page 4, lien, page actuelle ». La formulation varie d’un lecteur à l’autre, les informations non — nom de la région, nature de liste, rang, intitulé explicite, état courant.

Le diagnostic inverse se lit vite. Si vous n’entendez que des chiffres, les compléments masqués manquent. Si vous n’entendez pas « 3 sur 7 », il n’y a pas de liste — des <span> alignés, ou une <ol> privée de son rôle par list-style: none. Si vous n’entendez jamais « page actuelle », aria-current manque, ou la page en cours n’est pas un lien. Si vous entendez une URL, c’est un chevron sans intitulé.

Checklist

  • Un <nav> avec un aria-label qui ne répète pas le mot « navigation ».
  • Une <ol>, avec role="list" si les puces sont retirées en CSS.
  • Chaque numéro complété : nom accessible « Page 3 », pas « 3 ».
  • Les flèches nommées « Page précédente » et « suivante », chevron aria-hidden.
  • Le lien indisponible retiré du DOM, jamais grisé.
  • aria-current="page" sur la page en cours, et un signe visible non coloré.
  • Sans rechargement de page, une région role="status" présente dès le départ, et le focus replacé après le changement de page.

Les critères RGAA en jeu

Vérifiez votre composant avec l'extension

Pour aller plus loin

Sources