Aller au contenu
Me contacter

Toutes les briques

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

  1. h3 Le titre du composant, qui nomme la liste d'onglets.
  2. div role="tablist" La liste d'onglets, masquée par hidden tant que le script n'a pas tourné.
  3. button role="tab" Un onglet. Seul l'onglet sélectionné a tabindex="0" : c'est le seul arrêt de tabulation de la liste.
  4. div role="tabpanel" Le panneau de l'onglet sélectionné, nommé par son onglet. Les autres panneaux portent hidden="until-found", et leur titre h4 ne s'affiche que sans JavaScript.

Code HTML

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>
CSS
/* 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ôle tab.
  • 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émentDescription
role="tablist"divRegroupe les onglets.
aria-labelledby="tabs-title"div role="tablist"Donne à la liste le nom de son titre, « Lampe de bureau ».
role="tab"buttonLe 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"divAjouté 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

ToucheAction
TabEntre dans la liste, sur l'onglet sélectionné
Flèche gauche ou Flèche droiteOnglet précédent ou suivant, en boucle. Son panneau s'affiche aussitôt
Début ou FinPremier ou dernier onglet, affiché aussitôt
Tab depuis la listePasse au panneau affiché

Ordre de tabulation

  1. L'onglet sélectionné, « Description ». Les autres onglets s'atteignent avec les flèches.
  2. 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.

ActionAnnonce
Tab sur la liste« Description, sélectionné, onglet, 1 sur 5 »
Flèche droite« Dimensions, sélectionné, onglet, 2 sur 5 »
TabLe 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'état aria-selected, dans une liste au rôle tablist. Chaque panneau a le rôle tabpanel et 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

Au-delà de l'APG

NouveautéApportNavigateursDans 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
focusgroupGè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.

Code
<!-- 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.

Code
<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>