Navigation au clavier

La navigation au clavier (sans la souris) est essentielle en matière d'accessibilité numérique. En effet, certains utilisateurs, notamment ceux utilisant des logiciels de synthèse vocale, naviguent et interagissent avec l'interface web en uilisant uniquement le clavier.

Utilisation des touches du clavier pour la navigation

Pour naviguer au clavier, les touches suivantes sont utilisées :

  • Touche Tab (Tabulation) : pour passer au lien, élément de formulaire ou bouton suivant.
  • Touches Maj + Tab : pour passer au lien, élément de formulaire ou bouton précédent.
  • Touche Entrée : pour activer le lien ou le bouton.
  • Touche Espace : pour cocher / décocher une case à cocher ou pour déployer une liste déroulante.
  • Touches Flèche droite, Flèche gauche, Flèche bas, Flèche haut : pour faire défiler un affichage (ascenseur), pour modifier la valeur d’un bouton radio, pour se déplacer dans une liste (menu, liste d’option d’un liste de sélection déroulante…)
  • Touche Echap : pour retourner à l’état précédent (reprise de focus).

Lorsqu’un utilisateur se déplace dans la page web en utilisant le clavier, on parle alors de « focus clavier » pour désigner l’élément sur lequel le curseur est positionné.

Règles à respecter pour la navigation clavier

Les règles suivantes doivent être respectées pour l’accessibilité de la page :

  • Le focus clavier est toujours visible.
  • L’ordre de tabulation est cohérent avec le visuel ;
  • Tous les éléments interactifs (boutons, liens, champs de formulaire, fenêtre modale…) sont accessibles et fonctionnels au clavier ;
  • Les éléments HTML natifs sont privilégiés car prévus pour recevoir le focus (éléments a, button…) ;
  • Les raccourcis clavier utilisant une seule touche peuvent être modifiés ou désactivés par l’utilisateur ;

Gestion du focus clavier

Un focus clavier toujours visible

Le focus clavier doit toujours être visible. Une très mauvaise pratique consiste à le rendre invisible avec le code CSS suivant : :focus { outline: none;}. On veillera donc à ce que le focus clavier respecte les règles suivantes :

  • Couleur : sa couleur a un contraste minimum de 3:1 par rapport à l’élément d’arrière-plan.
  • Taille : sa taille doit faire minimum 2 fois le périmètre de la zone focusable. Ainsi, on déclarera une taille minimum de bordure de 2px.

remarque

La taille de la zone cliquable doit faire minimum 24px * 24px sauf si la zone cliquable se trouve dans un paragraphe (comme un lien à l'intérieur d'un bloc de texte).
Le critère AAA du WCAG va même jusquà demander une taille minimum de 44px * 44px.

:focus {
outline: 2px solid black;
}

Voici un style de focus qui fonctionne pour tous les cas :

:focus-visible {
    outline: 3px solid white;
    box-shadow: 0 0 0 6px black;
}

Un script javascript pour gérer le clavier

Le W3C met à disposion un script javascript qui permet de s’assurer des bonnes interactions au clavier. Le voici tel que trouvé sur leur site.

'use strict';
/**
 * @namespace aria
 */

var aria = aria || {};

/**
 * @description
 *  Key code constants
 */
aria.KeyCode = {
  BACKSPACE: 8,
  TAB: 9,
  RETURN: 13,
  SHIFT: 16,
  ESC: 27,
  SPACE: 32,
  PAGE_UP: 33,
  PAGE_DOWN: 34,
  END: 35,
  HOME: 36,
  LEFT: 37,
  UP: 38,
  RIGHT: 39,
  DOWN: 40,
  DELETE: 46,
};

aria.Utils = aria.Utils || {};

// Polyfill src https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
aria.Utils.matches = function (element, selector) {
  if (!Element.prototype.matches) {
    Element.prototype.matches =
      Element.prototype.matchesSelector ||
      Element.prototype.mozMatchesSelector ||
      Element.prototype.msMatchesSelector ||
      Element.prototype.oMatchesSelector ||
      Element.prototype.webkitMatchesSelector ||
      function (s) {
        var matches = element.parentNode.querySelectorAll(s);
        var i = matches.length;
        while (--i >= 0 && matches.item(i) !== this) {
          // empty
        }
        return i > -1;
      };
  }

  return element.matches(selector);
};

aria.Utils.remove = function (item) {
  if (item.remove && typeof item.remove === 'function') {
    return item.remove();
  }
  if (
    item.parentNode &&
    item.parentNode.removeChild &&
    typeof item.parentNode.removeChild === 'function'
  ) {
    return item.parentNode.removeChild(item);
  }
  return false;
};

aria.Utils.isFocusable = function (element) {
  if (element.tabIndex < 0) {
    return false;
  }

  if (element.disabled) {
    return false;
  }

  switch (element.nodeName) {
    case 'A':
      return !!element.href && element.rel != 'ignore';
    case 'INPUT':
      return element.type != 'hidden';
    case 'BUTTON':
    case 'SELECT':
    case 'TEXTAREA':
      return true;
    default:
      return false;
  }
};

aria.Utils.getAncestorBySelector = function (element, selector) {
  if (!aria.Utils.matches(element, selector + ' ' + element.tagName)) {
    // Element is not inside an element that matches selector
    return null;
  }

  // Move up the DOM tree until a parent matching the selector is found
  var currentNode = element;
  var ancestor = null;
  while (ancestor === null) {
    if (aria.Utils.matches(currentNode.parentNode, selector)) {
      ancestor = currentNode.parentNode;
    } else {
      currentNode = currentNode.parentNode;
    }
  }

  return ancestor;
};

aria.Utils.hasClass = function (element, className) {
  return new RegExp('(\\s|^)' + className + '(\\s|$)').test(element.className);
};

aria.Utils.addClass = function (element, className) {
  if (!aria.Utils.hasClass(element, className)) {
    element.className += ' ' + className;
  }
};

aria.Utils.removeClass = function (element, className) {
  var classRegex = new RegExp('(\\s|^)' + className + '(\\s|$)');
  element.className = element.className.replace(classRegex, ' ').trim();
};

aria.Utils.bindMethods = function (object /* , ...methodNames */) {
  var methodNames = Array.prototype.slice.call(arguments, 1);
  methodNames.forEach(function (method) {
    object[method] = object[method].bind(object);
  });
};