Lorsqu'on conçoit une interface web, l'accessibilité et l'expérience utilisateur doivent être au cœur des choix de design. L’une des bonnes pratiques en User eXperience/User Interface est l’adaptation de l’affichage des contours (outline).
En fonction du mode de navigation, les besoins ne sont pas du tout les mêmes.
- navigation clavier : il faut pouvoir visualiser clairement où se trouve le focus.
- navigation avec un pointeur (souris, trackpad) : le focus est intuitif et les contours visuels deviennent « inutiles ».
Le premier est une obligation légale de conformité, le second est une obligation graphique, compréhensible, mais qui amène à de fâcheux travers qui se retrouvent quasi systématiquement lors d'audits de conformité Référentiel général d’amélioration de l’accessibilité version 4.1.2 ou Web Content Accessibility Guidelines.
La solution la plus connue est d'utiliser, pour la partie Cascading Style Sheets, les pseudo classes :focus et :focus-visible. Ça fonctionne très bien, même si parfois un peu lourd à gérer.
Le concept
L'idée est d'utiliser une classe CSS (use-keyboard ou use-pointer) appliquée dynamiquement en JavaScript. Ces classes, configurables, sont appliquées sur la racine du document.
Grâce à ce mécanisme, on peut offrir une interface optimisée qui respecte les demandes esthétiques, tout en garantissant une accessibilité renforcée pour les utilisateurs qui en ont besoin.
Exemple simple
:root.use-keyboard *:focus {
outline: 2px solid blue; /* Visible uniquement en navigation clavier */
}
:root.use-pointer *:focus {
outline: none; /* Supprimé pour la navigation à la souris */
}
Version Kortic
Dans les styles globaux :
:root {
&.use-keyboard *:focus {
outline-offset: var(--outline-offset);
outline: var(--outline);
}
&:not(.use-keyboard) *:focus {
outline: none;
}
}
Ensuite, selon la charte :
:root.use-keyboard {
--outline-color: var(--orangered, orange);
--outline-offset: 0.125rem;
--outline-width: 0.25rem;
--outline-style: solid;
--outline: var(--outline-width) var(--outline-style) var(--outline-color);
}
/* avec gestion de contraste */
:root.use-keyboard[class*="contrast"] {
--outline-style: solid;
}
:root:not(.use-keyboard) {
/**
on garde une partie des variables utilisées ailleurs
*/
--outline-color: var(--orangered, orange);
--outline-offset: 0.0625rem;
--outline-width: 0.125rem;
--outline-style: dotted;
--outline: none;
}
Le script
Globalement, tout est expliqué dans le code.
Pour résumer :
- détection du type de terminal.
- si l'utilisateur n'utilise que le clavier, le script va utiliser la classe comme vu plus haut, même chose pour la navigation avec pointeur.
Dès que l'utilisateur utilise la toucheTab(tabulation), on considère que la navigation est effectuée au clavier.
À la détection d'un « pointeur », mobile ou souris, la navigation est considérée comme « standard ». - le script met également à jour une variable attachée à
window:window.userNavigatequi est donc disponible pour d'autres scripts. - création d'un événement
userNavigationChangeutilisable dans d'autres fonctions.
Taille brute : 3,29 kilo-octets (~1,30 kilo-octets gzip)
/**
* Source Kortic https://www.kortic.com/le-blog-kortic/2025/06/12/detection-du-mode-de-navigation-clavier-ou-pointeur-et-gestion-de-la-visibilite-du-focus.html
* licence Creative Commons CC BY-NC-SA 4.0
* https://creativecommons.org/licenses/by-nc-sa/4.0/deed.fr
* Attribution, partage dans les mêmes conditions, pas d'utilisation commerciale
*/
(() => {
'use strict';
/**
* Classe CSS utilisée pour indiquer la navigation clavier active.
* Les sélecteurs CSS associés :
* - .use-keyboard : navigation clavier active
* - :not(.use-keyboard) : navigation souris/tactile
*/
const CLASS_KEYBOARD = "use-keyboard";
const root = document.documentElement;
const body = document.body;
/**
* Déclenche un événement personnalisé "userNavigationChange"
* avec l'état de navigation clavier (true/false).
*/
window.dispatchUserNavigationChange = function (isKeyboard) {
document.dispatchEvent(new CustomEvent("userNavigationChange", {detail: isKeyboard}));
};
/**
* Active ou désactive le mode navigation clavier.
*/
window.updateNavigation = function (isKeyboard) {
if (root.classList.contains(CLASS_KEYBOARD) === isKeyboard) return;
window.useKeyboard = isKeyboard;
root.classList.toggle(CLASS_KEYBOARD, isKeyboard);
localStorage.setItem(`${CLASS_KEYBOARD}`, isKeyboard);
window.dispatchUserNavigationChange(isKeyboard);
};
document.addEventListener("DOMContentLoaded", () => {
/**
* Détection des appareils tactiles :
* - ontouchstart : présence d'événements tactiles
* - pointer coarse : pointeur grossier (souvent tactile)
* - userAgent : détection des appareils mobiles
*/
const tactile = "ontouchstart" in window
&& matchMedia("(pointer: coarse)").matches
&& /Android|iPhone|iPad|iPod|Windows Phone|BlackBerry|Opera Mini|Mobile|Mobi|Kindle|Silk|PlayBook|webOS/i.test(navigator.userAgent);
/**
* Récupère la dernière préférence utilisateur ou détermine
* le mode par défaut (clavier par défaut sur desktop).
*/
const lastInteraction = localStorage.getItem(`${CLASS_KEYBOARD}`);
window.updateNavigation(lastInteraction === "true" || (!tactile && lastInteraction === null));
/**
* Écoute les interactions souris/tactile pour désactiver le mode clavier
* sauf si l'appareil est tactile (évite les faux positifs).
*/
const pointerHandler = () => {
if (!tactile) window.updateNavigation(false);
};
body.addEventListener("pointerdown", pointerHandler, {passive: true});
body.addEventListener("mousedown", pointerHandler, {passive: true});
/**
* Écoute les touches de navigation pour activer le mode clavier
* sauf si l'utilisateur est dans un champ de formulaire.
*/
const hasKeyShortcuts = document.querySelector("[aria-keyshortcuts]") !== null;
body.addEventListener("keydown", event => {
const navigationKeys = [
"Tab", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight",
"Home", "End", "PageUp", "PageDown"
];
const isFormField = ["TEXTAREA", "INPUT", "SELECT"].includes(document.activeElement.tagName);
if (hasKeyShortcuts && isFormField) {
// Lettres utilisées par des aria-keyshortcuts, à ignorer dans les champs de formulaire
const excludedKeys = ["b", "c", "d", "f", "g", "h", "i", "m", "n", "o", "s", "t", "w"];
if (excludedKeys.includes(event.key)) return;
}
if (navigationKeys.includes(event.key) && !isFormField) {
window.updateNavigation(true);
}
}, {passive: true});
});
})();
Télécharger kortic-kbd-navigation.js
Applications
Dans les feuilles de style, bien entendu. Mais pas que !
Par exemple, les calendriers personnalisés (DatePicker) sont compliqués à utiliser en naviguant au clavier, voir inutilisables. Il existe un input de type date Hyper Text Language Markup qui est totalement natif et compatible pour l'accessibilité.
À l'aide de cette variable et/ou des CSS, il devient possible de ne proposer le datepicker complexe qu'aux personnes pouvant l'utiliser à la souris, et une version totalement accessible avec, par exemple, 2 champs de saisie date.
Autre application possible, gérer les interactions sur l'affichage de sous-menus d'une navigation. Etc.
Tester
Naviguer normalement sur Kortic, cliquer partout…
Ensuite, lâcher la souris, recharger la page (ou pas) et utiliser la navigation clavier, touche Touche clavier Tabulation ⇥ , et regarder…
That's all Folks !