Chapitre 30 : Gestion unifiée du focus et accessibilité : focus-within et focus-visible
Maîtriser la gestion complète du focus via :focus-within et :focus-visible pour concilier esthétique moderne et accessibilité inclusive.
L'accessibilité (a11y) n'est pas une option, c'est une fondation. L'un des piliers de l'accessibilité web est la navigation au clavier. Pour un utilisateur naviguant avec la touche Tab, savoir exactement où se trouve le focus est crucial. Cependant, pour les designers, l'anneau de focus par défaut des navigateurs est souvent jugé "laid" ou intrusif pour les utilisateurs de souris.
C'est ici qu'interviennent :focus-visible et :focus-within. Ces deux pseudo-classes permettent de créer des interfaces intelligentes qui s'adaptent à la méthode de saisie de l'utilisateur et à la structure hiérarchique du DOM.
La pseudo-classe :focus-visible
1. Quoi
La pseudo-classe :focus-visible est un sélecteur qui s'applique à un élément lorsqu'il reçoit le focus et que le navigateur détermine que l'indicateur de focus doit être visible.
Contrairement à :focus, qui s'active systématiquement dès qu'un élément est focalisé (que ce soit via un clic de souris ou une touche Tab), :focus-visible utilise une heuristique intelligente :
- Clavier : Si l'utilisateur utilise
Tab,:focus-visibleest activé. - Souris/Tactile : Si l'utilisateur clique sur un bouton,
:focus-visiblen'est généralement pas activé (car le clic indique déjà visuellement l'interaction).
2. Pourquoi
Dans un projet professionnel, on souhaite souvent supprimer l'outline (le contour bleu/noir) pour les utilisateurs de souris afin d'épurer le design, tout en le conservant impérativement pour les utilisateurs de clavier.
L'utilisation de outline: none sur :focus est une erreur grave d'accessibilité, car elle rend le site inutilisable pour les personnes malvoyantes ou handicapées motrices. :focus-visible résout ce dilemme en permettant de cibler uniquement les utilisateurs qui en ont réellement besoin.
3. Comment
A. Syntaxe de base
L'idée est de supprimer le focus par défaut et de le redéfinir uniquement pour le mode "visible".
/* On retire l'outline pour tout focus (Attention : danger si non compensé) */
.btn:focus {
outline: none;
}
/* On le réactive uniquement quand le navigateur juge que c'est nécessaire */
.btn:focus-visible {
outline: 3px solid #3b82f6;
outline-offset: 2px;
}
B. Cas concret : Composant de navigation accessible
Voici comment implémenter un menu de navigation où les liens ne montrent un indicateur de focus que lors d'une navigation au clavier.
.nav-link {
color: #4b5563;
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
transition: all 0.2s ease;
}
/* État de survol (souris) */
.nav-link:hover {
background-color: #f3f4f6;
color: #1f2937;
}
/*
On neutralise le focus standard pour éviter l'anneau
lors d'un clic simple
*/
.nav-link:focus {
outline: none;
}
/*
On crée un indicateur fort et distinct pour la navigation clavier.
L'utilisation de box-shadow est souvent préférée à outline
pour un meilleur contrôle esthétique.
*/
.nav-link:focus-visible {
outline: 2px solid #2563eb;
outline-offset: 2px;
background-color: #eff6ff;
color: #1e40af;
}
C. Limitations
- Compatibilité :
:focus-visibleest désormais supporté par tous les navigateurs modernes. Pour les versions très anciennes, on peut utiliser un polyfill ou s'appuyer sur:focuspar défaut. - Heuristiques variables : Le comportement peut légèrement varier selon le navigateur (certains navigateurs activent
:focus-visiblesur les champs de texte même lors d'un clic, car la saisie clavier est attendue).
4. Zone de Danger
❌ L'erreur fatale :
/* NE JAMAIS FAIRE CECI */
*:focus {
outline: none;
}
Pourquoi ? Vous venez de rendre votre site invisible pour tous les utilisateurs de clavier.
✅ La bonne pratique :
/* On retire l'outline seulement si on le remplace par :focus-visible */
.element:focus {
outline: none;
}
.element:focus-visible {
outline: 2px solid blue;
}
La pseudo-classe :focus-within
1. Quoi
La pseudo-classe :focus-within s'applique à un élément si cet élément lui-même OU l'un de ses descendants a le focus.
C'est un sélecteur "ascendant". Alors que :focus regarde l'élément cible, :focus-within regarde l'élément et tout son sous-arbre DOM.
2. Pourquoi
C'est extrêmement utile pour styliser un conteneur parent en fonction de l'activité à l'intérieur.
Exemples d'usage :
- Mettre en évidence un formulaire entier lorsqu'un champ est rempli.
- Afficher un menu déroulant ou des options supplémentaires quand un élément interne est focalisé.
- Changer la couleur de fond d'une carte (card) quand l'utilisateur navigue dans les boutons de cette carte.
3. Comment
A. Syntaxe de base
.parent:focus-within {
background-color: #f0f9ff;
border-color: #0ea5e9;
}
B. Cas concret : Formulaire de recherche sophistiqué
Imaginons un champ de recherche entouré d'une icône et d'un bouton. Nous voulons que tout le bloc change d'apparence dès que l'utilisateur commence à interagir avec l'input.
<div class="search-container">
<span class="search-icon">🔍</span>
<input type="text" placeholder="Rechercher un cours..." class="search-input" />
<button class="search-submit">OK</button>
</div>
.search-container {
display: flex;
align-items: center;
padding: 8px 16px;
border: 2px solid #d1d5db;
border-radius: 9999px;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
background-color: white;
}
.search-input {
border: none;
outline: none; /* On retire l'outline de l'input car le parent gère le visuel */
padding: 4px 8px;
flex-grow: 1;
}
/*
Dès que l'input OU le bouton reçoit le focus,
le conteneur parent s'illumine.
*/
.search-container:focus-within {
border-color: #3b82f6;
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
}
.search-icon {
color: #9ca3af;
transition: color 0.3s ease;
}
/* On peut même styliser des enfants en fonction du focus-within du parent */
.search-container:focus-within .search-icon {
color: #3b82f6;
}
C. Limitations
- Performance : Sur des DOM extrêmement profonds et complexes, l'utilisation massive de
:focus-withinpeut théoriquement impacter les performances de rendu, bien que cela soit rare en pratique. - Sémantique : Assurez-vous que le changement visuel du parent ne perturbe pas la compréhension de l'élément qui a réellement le focus.
4. Zone de Danger
❌ L'erreur de confusion : Confondre :focus-within avec :has(:focus).
Bien que :has(:focus) fasse presque la même chose, :focus-within est beaucoup plus performant et mieux supporté pour ce cas précis.
✅ La bonne pratique : Utiliser :focus-within pour tout ce qui concerne la propagation du focus vers le haut de la hiérarchie.
Logique de déclenchement du Focus Moderne
Questions clés
1. Quelle est la différence fondamentale entre :focus et :focus-visible ?
Découvrir la réponse
:focus s'active systématiquement lors de la focalisation d'un élément, peu importe la méthode (clic ou clavier). :focus-visible ne s'active que si le navigateur estime qu'un indicateur visuel est nécessaire pour l'utilisateur (principalement lors de l'utilisation du clavier).
2. Pourquoi :focus-within est-il utile pour l'expérience utilisateur (UX) ?
Découvrir la réponse
Il permet de donner un contexte visuel global. Par exemple, si un utilisateur navigue dans un formulaire complexe, illuminer le groupe de champs (le parent) aide à identifier quelle section du formulaire est en cours de saisie, améliorant ainsi la clarté cognitive.
3. Est-il acceptable de mettre outline: none sur tous les éléments ?
Découvrir la réponse
Absolument pas. Cela brise l'accessibilité. On ne peut retirer l'outline que si on fournit une alternative visuelle claire via :focus-visible pour les utilisateurs de clavier.
4. Peut-on combiner :focus-within et :focus-visible ?
Découvrir la réponse
Oui. On peut utiliser :focus-within sur un parent pour changer sa couleur de fond, tout en utilisant :focus-visible sur les enfants pour afficher un anneau de focus précis sur l'élément actif.
5. Comment :focus-within se comporte-t-il avec les éléments imbriqués ?
Découvrir la réponse
Si n'importe quel élément à l'intérieur du conteneur (même un petit bouton dans une div, dans une autre div) reçoit le focus, le parent le plus haut possédant la règle :focus-within sera stylisé.
Mise en pratique
Exercice 1 : Reproduction guidée
Objectif : Créer un bouton "Call to Action" qui n'affiche son contour bleu que lors d'une navigation au clavier.
- Créez un bouton avec un fond bleu et texte blanc.
- Supprimez l'outline par défaut au
:focus. - Ajoutez un
outlinede 3px bleu foncé avec unoutline-offsetde 2px uniquement via:focus-visible.
Découvrir la solution commentée
.cta-button {
background-color: #3b82f6;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
}
/* On retire l'outline pour le clic souris */
.cta-button:focus {
outline: none;
}
/* On le rétablit pour le clavier pour l'accessibilité */
.cta-button:focus-visible {
outline: 3px solid #1d4ed8;
outline-offset: 2px;
}
Exercice 2 : Adaptation
Objectif : Créer une "Card" de produit contenant un lien "Voir plus". La carte doit changer de bordure et d'ombre dès que le lien interne est focalisé.
- Créez une div
.product-cardavec un lien<a>à l'intérieur. - Utilisez
:focus-withinsur.product-cardpour passer la bordure de gris à orange et ajouter une ombre portée (box-shadow). - Assurez-vous que le lien lui-même utilise
:focus-visiblepour son propre indicateur.
Découvrir la solution commentée
.product-card {
border: 1px solid #ccc;
padding: 20px;
border-radius: 8px;
transition: all 0.3s ease;
width: 200px;
}
/* Le parent réagit quand l'enfant est focalisé */
.product-card:focus-within {
border-color: orange;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.product-link {
color: blue;
text-decoration: none;
}
.product-link:focus {
outline: none;
}
.product-link:focus-visible {
outline: 2px solid orange;
border-radius: 2px;
}
Exercice 3 : Conception
Objectif : Concevoir un composant de "Groupe de boutons" (Button Group) accessible.
- Créez un conteneur
.btn-groupcontenant trois boutons. - Le conteneur doit avoir un fond gris léger.
- Quand un bouton à l'intérieur est focalisé :
- Le fond du conteneur
.btn-groupdoit devenir blanc. - Le bouton focalisé doit être mis en évidence via
:focus-visible. - Les autres boutons (non focalisés) dans le groupe doivent légèrement baisser leur opacité pour accentuer le focus sur l'élément actif.
- Le fond du conteneur
Découvrir la solution commentée
.btn-group {
display: inline-flex;
background-color: #e5e7eb;
padding: 4px;
border-radius: 8px;
transition: background-color 0.2s ease;
}
/* 1. Fond du conteneur change quand un enfant a le focus */
.btn-group:focus-within {
background-color: #ffffff;
box-shadow: 0 0 0 2px #d1d5db;
}
.btn-group button {
padding: 8px 16px;
border: none;
background: transparent;
cursor: pointer;
transition: opacity 0.2s ease;
}
/* 2. Focus visible spécifique pour le bouton */
.btn-group button:focus {
outline: none;
}
.btn-group button:focus-visible {
outline: 2px solid #2563eb;
background-color: #eff6ff;
}
/* 3. Baisse d'opacité des autres boutons quand le groupe est focalisé */
/* On cible tous les boutons quand le parent a le focus */
.btn-group:focus-within button {
opacity: 0.6;
}