Bac à sable
Les onglets
Le HTML n'a pas d'élément onglet, et des boutons radio stylés en onglets restent des boutons radio pour le lecteur d'écran. Ici, de vrais boutons reçoivent les rôles ARIA tablist, tab et tabpanel, et un court script gère le clavier comme le décrit l'APG.
- Technique
- tab, tablist et tabpanel
- Navigateurs
- Chrome 105+, Firefox 121+, Safari 15.4+
- JavaScript
- 32 lignes, pour le clavier et la sélection
Démo
Désactive JavaScript : la liste d'onglets disparaît, et les 5 panneaux s'affichent l'un sous l'autre, chacun sous son titre.
Lampe de bureau
Description
Une lampe orientable, avec 3 niveaux de lumière.
Dimensions
45 cm de haut, sur un pied de 15 cm de diamètre.
Composition
Pied en acier, abat-jour en lin.
Entretien
Un chiffon sec suffit. Débranchez la lampe avant de la nettoyer.
Livraison
Expédiée sous 48 h, livrée en 3 à 5 jours ouvrés.
Anatomie
h3Le titre du composant, qui nomme la liste d'onglets.div role="tablist"La liste d'onglets, masquée parhiddentant que le script n'a pas tourné.button role="tab"Un onglet. Seul l'onglet sélectionné atabindex="0": c'est le seul arrêt de tabulation de la liste.div role="tabpanel"Le panneau de l'onglet sélectionné, nommé par son onglet. Les autres panneaux portenthidden="until-found", et leur titreh4ne s'affiche que sans JavaScript.
Code HTML
<div class="tabs">
<h3 id="tabs-title">Lampe de bureau</h3>
<!-- Liste d'onglets : masquée tant que le script n'a pas tourné -->
<div
class="tabs__list"
role="tablist"
aria-labelledby="tabs-title"
hidden>
<!-- Onglet sélectionné : le seul arrêt de tabulation de la liste -->
<button
id="tab-1"
type="button"
role="tab"
aria-selected="true"
aria-controls="tabpanel-1">
Description
</button>
<button
id="tab-2"
type="button"
role="tab"
aria-selected="false"
aria-controls="tabpanel-2"
tabindex="-1">
Dimensions
</button>
<!-- Onglets 3 à 5 : même modèle -->
</div>
<!-- Panneaux : sans script, ils s'affichent tous, chacun sous son titre -->
<div id="tabpanel-1" class="tabs__panel">
<h4 class="tabs__title">Description</h4>
<p>…</p>
</div>
<div id="tabpanel-2" class="tabs__panel">
<h4 class="tabs__title">CSS</h4>
<p>…</p>
</div>
</div>
<script>
// Activation automatique : l'onglet qui reçoit le focus s'affiche
const list = document.querySelector('.tabs [role="tablist"]');
const tabs = [...list.querySelectorAll('[role="tab"]')];
const panel = (t) => document.getElementById(t.getAttribute('aria-controls'));
const select = (tab) => {
for (const t of tabs) {
t.setAttribute('aria-selected', t === tab);
t.tabIndex = t === tab ? 0 : -1;
// until-found : Ctrl+F trouve le texte d'un panneau masqué.
// aria-hidden retire de l'arbre d'accessibilité le panneau vide.
if (t === tab) {
panel(t).removeAttribute('hidden');
panel(t).removeAttribute('aria-hidden');
panel(t).tabIndex = 0;
} else {
panel(t).setAttribute('hidden', 'until-found');
panel(t).setAttribute('aria-hidden', 'true');
panel(t).removeAttribute('tabindex');
}
}
};
list.addEventListener('click', (e) => {
const tab = e.target.closest('[role="tab"]');
if (tab) select(tab);
});
list.addEventListener('keydown', (e) => {
const i = tabs.indexOf(e.target);
const n = tabs.length;
const keys = { ArrowLeft: i - 1, ArrowRight: i + 1, Home: 0, End: n - 1 };
const next = keys[e.key];
if (next === undefined) return;
e.preventDefault();
const tab = tabs[(next + n) % n];
tab.focus();
select(tab);
});
// Panneaux : rôle et nom. Si Ctrl+F trouve du texte
// dans un panneau masqué, son onglet s'affiche.
for (const t of tabs) {
panel(t).setAttribute('role', 'tabpanel');
panel(t).setAttribute('aria-labelledby', t.id);
panel(t).addEventListener('beforematch', () => select(t));
}
list.hidden = false;
select(tabs[0]);
</script>/* Avec le script, la liste d'onglets remplace les titres des panneaux */
.tabs:has(.tabs__list:not([hidden])) .tabs__title {
display: none;
}
.tabs__list:not([hidden]) {
display: flex;
gap: 4px;
}
/* L'onglet sélectionné : couleur et barre en haut de l'onglet */
.tabs [aria-selected="true"] {
background: var(--accent);
color: var(--on-accent);
box-shadow: inset 0 4px 0 var(--highlight);
}
.tabs [role="tab"]:focus-visible,
.tabs [role="tabpanel"]:focus-visible {
outline: 3px solid var(--highlight);
}Usages
Recommandé pour
- Des contenus de même niveau, dont un seul panneau est utile à la fois.
- Des libellés courts, qui tiennent sur une ligne.
Déconseillé pour
- Des contenus à comparer : il faut garder le premier en mémoire pour lire le second.
- Des onglets qui changent l'adresse de la page. Ce sont des liens de navigation, avec
aria-current="page"sur la page active, sans rôletab. - Distinguer l'onglet sélectionné par la couleur seule.
- Des boutons radio stylés en onglets : le lecteur d'écran annonce des boutons radio, et l'onglet perd son lien avec son panneau.
- Un
tabindex="0"sur chaque onglet : Tab s'arrête sur tous les onglets avant d'atteindre le panneau.
Accessibilité
Rôles et attributs ARIA
Les rôles de la liste et des onglets sont dans le HTML, mais la liste reste masquée par hidden tant que le script n'a pas tourné. Le script ajoute le rôle tabpanel aux panneaux, puis tient à jour aria-selected, tabindex, hidden et aria-hidden.
| Attribut | Élément | Description |
|---|---|---|
role="tablist" | div | Regroupe les onglets. |
aria-labelledby="tabs-title" | div role="tablist" | Donne à la liste le nom de son titre, « Lampe de bureau ». |
role="tab" | button | Le bouton devient un onglet. Le lecteur d'écran reçoit sa position dans la liste, « 2 sur 5 ». |
aria-selected="true" | button role="tab" | Sur l'onglet affiché, et sur lui seul : les autres portent aria-selected="false". Le script le déplace à chaque changement d'onglet. |
aria-controls="tabpanel-1" | button role="tab" | Relie l'onglet à son panneau, par l'id du panneau. |
tabindex="-1" | button role="tab" | Sur les onglets non sélectionnés : Tab n'entre dans la liste qu'une fois, sur l'onglet sélectionné. |
role="tabpanel" | div | Ajouté par le script : sans JavaScript, le panneau reste un simple bloc sous son titre. |
aria-labelledby="tab-1" | div role="tabpanel" | Le panneau prend le nom de son onglet. |
tabindex="0" | div role="tabpanel" | Sur le panneau affiché, qui ne contient ni lien ni bouton : Tab s'y arrête pour qu'on le lise. |
hidden="until-found" | div role="tabpanel" | Masque les panneaux des onglets non sélectionnés. La recherche du navigateur (Ctrl+F) trouve leur texte, déclenche l'événement beforematch, et le script affiche l'onglet. Les navigateurs qui ne le connaissent pas le traitent comme hidden. |
aria-hidden="true" | div role="tabpanel" masqué | Chrome garde un panneau until-found dans l'arbre d'accessibilité, vide : aria-hidden l'en retire. |
aria-orientation="vertical" | div role="tablist" | Dans la variation Onglets verticaux : la liste s'annonce verticale, et se parcourt avec les flèches haut et bas. |
Navigation au clavier
| Touche | Action |
|---|---|
| Tab | Entre dans la liste, sur l'onglet sélectionné |
| Flèche gauche ou Flèche droite | Onglet précédent ou suivant, en boucle. Son panneau s'affiche aussitôt |
| Début ou Fin | Premier ou dernier onglet, affiché aussitôt |
| Tab depuis la liste | Passe au panneau affiché |
Ordre de tabulation
- L'onglet sélectionné, « Description ». Les autres onglets s'atteignent avec les flèches.
- Le panneau affiché, qui reçoit le focus pour être lu.
Avec un lecteur d'écran
Exemple sur Chrome avec VoiceOver. Les mots changent d'un lecteur d'écran à l'autre.
| Action | Annonce |
|---|---|
| Tab sur la liste | « Description, sélectionné, onglet, 1 sur 5 » |
| Flèche droite | « Dimensions, sélectionné, onglet, 2 sur 5 » |
| Tab | Le nom du panneau, « Dimensions », puis son texte |
Critères RGAA concernés
- Critère 3.1 : Dans chaque page web, l’information ne doit pas être donnée uniquement par la couleur. Cette règle est-elle respectée ?
- L'onglet sélectionné se repère sans la couleur, par la barre en haut de l'onglet.
- Critère 7.1 : Chaque script est-il, si nécessaire, compatible avec les technologies d’assistance ?
- Les onglets ont le rôle
tab, un nom et l'étataria-selected, dans une liste au rôletablist. Chaque panneau a le rôletabpanelet le nom de son onglet. - Critère 7.3 : Chaque script est-il contrôlable par le clavier et par tout dispositif de pointage (hors cas particuliers) ?
- Les onglets se commandent au clavier, avec les flèches, Début et Fin, comme le décrit l'APG.
- Critère 10.7 : Dans chaque page web, pour chaque élément recevant le focus, la prise de focus est-elle visible ?
- Le focus reste visible sur l'onglet et sur le panneau.
- Critère 10.8 : Pour chaque page web, les contenus cachés ont-ils vocation à être ignorés par les technologies d’assistance ?
- Les panneaux masqués sont ignorés par les technologies d'assistance.
- Critère 12.8 : Dans chaque page web, l’ordre de tabulation est-il cohérent ?
- L'ordre de tabulation suit l'ordre visuel : l'onglet sélectionné, puis son panneau.
Autres références
WCAG 2.2
- Understanding SC 1.3.1: Info and Relationships (en anglais)
- Understanding SC 1.4.1: Use of Color (en anglais)
- Understanding SC 2.1.1: Keyboard (en anglais)
- Understanding SC 2.4.3: Focus Order (en anglais)
- Understanding SC 2.4.7: Focus Visible (en anglais)
- Understanding SC 4.1.2: Name, Role, Value (en anglais)
EN 301 549
- Accessibility requirements for ICT products and services (V4.1.1, PDF) (en anglais)Clauses 9.1.3.1, 9.1.4.1, 9.2.1.1, 9.2.4.3, 9.2.4.7, 9.4.1.2
Pattern APG
- Tabs Pattern (en anglais)
- Example of Tabs with Automatic Activation (en anglais)
- Example of Tabs with Manual Activation (en anglais)
MDN
- ARIA: tab role (en anglais)
- ARIA: tablist role (en anglais)
- ARIA: tabpanel role (en anglais)
Au-delà de l'APG
| Nouveauté | Apport | Navigateurs | Dans la démo |
|---|---|---|---|
hidden="until-found" | La recherche du navigateur (Ctrl+F) trouve le texte d'un panneau masqué, et le script affiche son onglet. | Chrome 102+, Firefox 148+ | Oui |
focusgroup | Gère en HTML les flèches et l'arrêt de tabulation unique de la liste d'onglets, sans script. | Chrome 150+, Edge 150+ | Non |
Variations
Onglets verticaux
La liste passe en colonne, à gauche du panneau, et aria-orientation="vertical" l'annonce. Les flèches haut et bas remplacent gauche et droite.
Lampe de bureau
Description
Une lampe orientable, avec 3 niveaux de lumière.
Dimensions
45 cm de haut, sur un pied de 15 cm de diamètre.
Composition
Pied en acier, abat-jour en lin.
Entretien
Un chiffon sec suffit. Débranchez la lampe avant de la nettoyer.
Livraison
Expédiée sous 48 h, livrée en 3 à 5 jours ouvrés.
<!-- HTML -->
<div
class="tabs__list"
role="tablist"
aria-labelledby="tabs-title"
aria-orientation="vertical"
hidden>
/* CSS */
/* Avec le script, la liste passe en colonne à gauche du panneau */
.tabs:has(.tabs__list:not([hidden])) {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
}
.tabs > h3 {
grid-column: 1 / -1;
}
.tabs__list:not([hidden]) {
flex-direction: column;
}
<script>
// Flèches haut et bas à la place de gauche et droite
const keys = { ArrowUp: i - 1, ArrowDown: i + 1, Home: 0, End: n - 1 };
</script>Activation manuelle
Les flèches déplacent seulement le focus, et Entrée ou Espace affiche le panneau. L'APG la conseille quand un panneau met du temps à s'afficher, par exemple s'il se charge depuis le serveur.
Lampe de bureau
Description
Une lampe orientable, avec 3 niveaux de lumière.
Dimensions
45 cm de haut, sur un pied de 15 cm de diamètre.
Composition
Pied en acier, abat-jour en lin.
Entretien
Un chiffon sec suffit. Débranchez la lampe avant de la nettoyer.
Livraison
Expédiée sous 48 h, livrée en 3 à 5 jours ouvrés.
Les flèches déplacent le focus : Entrée ou Espace affiche le panneau.
<script>
// Les flèches déplacent seulement le focus.
// Entrée ou Espace déclenche le clic de l'onglet, qui l'affiche.
list.addEventListener('keydown', (e) => {
const i = tabs.indexOf(e.target);
const n = tabs.length;
const keys = { ArrowLeft: i - 1, ArrowRight: i + 1, Home: 0, End: n - 1 };
const next = keys[e.key];
if (next === undefined) return;
e.preventDefault();
tabs[(next + n) % n].focus();
});
</script>