Skip to main content

Chapitre 72 : Accessibilité des contenus masqués avec clip-path et sr-only

Techniques CSS pour masquer des éléments visuellement tout en les rendant accessibles aux lecteurs d'écran

Dans le développement d'interfaces modernes, il arrive fréquemment qu'une information soit évidente visuellement grâce au contexte, aux icônes ou à la mise en page, mais qu'elle soit totalement absente ou ambiguë pour un utilisateur utilisant un lecteur d'écran (Screen Reader).

L'enjeu pour un développeur Senior est de savoir masquer un élément pour les utilisateurs voyants sans pour autant le supprimer de l'Accessibility Tree (l'arbre d'accessibilité), car des propriétés comme display: none ou visibility: hidden retirent l'élément de l'expérience utilisateur globale, y compris pour les technologies d'assistance.

Le pattern sr-only et le masquage visuel

1. Quoi

Le pattern sr-only (ou visually-hidden) est une classe utilitaire CSS conçue pour masquer un élément de manière à ce qu'il ne soit pas rendu graphiquement à l'écran, tout en restant parfaitement détectable et lisible par les lecteurs d'écran.

Contrairement au masquage total, cette technique ne modifie pas la nature sémantique de l'élément et ne l'extrait pas du flux logique de navigation du clavier.

2. Pourquoi

L'utilité professionnelle de ce pattern se manifeste dans plusieurs cas critiques :

  • Icônes sans texte : Un bouton représenté par une loupe est compréhensible visuellement, mais un lecteur d'écran lira simplement "Bouton". On ajoute alors un texte sr-only comme "Rechercher sur le site".
  • Labels de formulaires : Pour un design minimaliste, on peut vouloir masquer le label d'un champ input si l'indication est donnée par un placeholder ou une icône, tout en maintenant le lien sémantique for/id pour l'accessibilité.
  • Liens de saut (Skip Links) : Permettre aux utilisateurs de clavier de sauter la navigation principale pour accéder directement au contenu, en masquant le lien jusqu'à ce qu'il reçoive le focus.
  • Indices de contexte : Ajouter des précisions sur des liens répétitifs (ex: "Lire la suite" devient "Lire la suite de l'article sur le CSS").

3. Comment

A. Syntaxe de base (Le standard moderne)

Voici l'implémentation robuste et moderne utilisant clip-path.

.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0); /* Support legacy */
clip-path: inset(50%); /* Standard moderne */
white-space: nowrap;
border-width: 0;
}

B. Cas concret : Bouton de recherche accessible

Imaginons un header où seul une icône SVG est présente pour le bouton de recherche.

<form role="search" class="search-form">
<label for="search-input" class="sr-only">Rechercher un article</label>
<input type="search" id="search-input" name="q" placeholder="Recherche...">

<button type="submit">
<svg aria-hidden="true" focusable="false" width="20" height="20">

<circle cx="10" cy="10" r="8" stroke="black" fill="none" />
<line x1="16" y1="16" x2="20" y2="20" stroke="black" />
</svg>
<span class="sr-only">Lancer la recherche</span>
</button>
</form>

C. Limitations et pièges

Le masquage visuel n'est pas une solution miracle. Voici les points de vigilance :

  1. Le Focus : Si vous appliquez .sr-only à un élément focusable (comme un lien <a> ou un bouton <button>), l'élément restera focusable via la touche Tab. Si l'élément est totalement invisible, l'utilisateur de clavier verra son focus "disparaître" de l'écran, ce qui est une expérience utilisateur catastrophique.
  2. L'espace occupé : L'utilisation de position: absolute et de dimensions minimales (1px) est cruciale pour éviter que l'élément ne crée des espaces vides ou des décalages de mise en page.
  3. L'abus de texte : Ajouter trop de texte sr-only peut rendre l'expérience des utilisateurs de lecteurs d'écran verbeuse et pénible.

4. Zone de Danger

Erreur commune : Utiliser opacity: 0 ou color: transparent L'élément reste présent visuellement (il occupe de l'espace) et peut être cliqué, mais il est invisible. Pire, certains lecteurs d'écran peuvent ignorer les éléments dont l'opacité est à 0.

Bonne pratique : Le combo clip-path + absolute C'est la seule méthode qui garantit que l'élément est retiré du rendu visuel tout en restant dans l'arbre d'accessibilité.


Logique de choix du masquage CSS

Analyse technique approfondie : clip vs clip-path

Pour un profil Senior, il est essentiel de comprendre pourquoi on utilise clip-path: inset(50%) plutôt qu'un simple display: none.

Le fonctionnement de clip-path

La propriété clip-path crée une région de masque. Tout ce qui se trouve en dehors de cette région est rendu invisible. L'utilisation de inset(50%) réduit la zone visible à un point central infinitésimal, rendant l'élément invisible pour l'œil humain.

Cependant, contrairement à display: none, l'élément :

  1. Conserve sa place dans le DOM.
  2. Est toujours traité par le moteur de rendu pour le calcul de l'accessibilité.
  3. Permet aux lecteurs d'écran de parcourir le contenu linéairement.

Comparatif des méthodes de masquage

PropriétéVisuelLecteur d'écranFocus ClavierImpact Layout
display: noneSupprimé
visibility: hiddenConservé
opacity: 0Conservé
.sr-onlyMinimal (1px)

Gestion du focus et "Skip Links"

L'un des cas d'usage les plus avancés du sr-only est le Skip Link. C'est un lien placé au tout début du document qui permet d'accéder directement au contenu principal (<main>), évitant ainsi de tabuler 50 fois à travers le menu de navigation.

Le lien doit être sr-only par défaut, mais devenir visible lorsqu'il reçoit le focus.

/* État initial : masqué visuellement */
.skip-link {
position: absolute;
top: -1000px; /* On le sort loin de l'écran */
left: 0;
background: #000;
color: #fff;
padding: 1rem;
z-index: 100;
text-decoration: none;
}

/* État focus : on le ramène dans le viewport */
.skip-link:focus {
top: 0;
position: static; /* Ou position: fixed selon le design */
}

Note : On préfère ici top: -1000px ou clip-path plutôt que display: none car l'élément doit être capable de recevoir le focus clavier dès le chargement de la page.

Questions clés

1. Pourquoi ne pas utiliser display: none pour masquer un label de formulaire ?

Découvrir la réponse

Parce que display: none retire l'élément de l'arbre d'accessibilité. Un utilisateur de lecteur d'écran ne saura pas à quoi correspond le champ de saisie, car le label associé n'existera plus pour lui.

2. Quelle est la différence entre clip et clip-path ?

Découvrir la réponse

clip est une propriété obsolète (legacy) qui ne fonctionne que sur des éléments positionnés. clip-path est le standard moderne, plus flexible et mieux supporté par les navigateurs actuels, permettant des masques complexes.

3. Un élément .sr-only peut-il être cliqué par un utilisateur voyant ?

Découvrir la réponse

Techniquement oui, s'il s'agit d'un lien ou d'un bouton, car il occupe toujours 1px par 1px sur la page. Cependant, avec overflow: hidden et un positionnement absolu, la probabilité de cliquer dessus accidentellement est quasi nulle.

4. Comment s'assurer qu'une icône SVG ne perturbe pas la lecture du screen reader ?

Découvrir la réponse

Il faut ajouter l'attribut aria-hidden="true" sur le SVG. Cela indique au lecteur d'écran d'ignorer l'élément graphique, laissant ainsi le texte contenu dans la classe .sr-only fournir l'information nécessaire.

Mise en pratique

Exercice 1 : Reproduction guidée

Objectif : Créer une classe .visually-hidden respectant les standards d'accessibilité et l'appliquer à un titre de section qui est implicite visuellement.

Consigne :

  1. Implémentez la classe CSS .visually-hidden (utilisez le pattern vu dans le cours).
  2. Créez une structure HTML avec trois sections. Chaque section a un titre <h2> qui doit être masqué visuellement mais lisible par un lecteur d'écran.
Découvrir la solution commentée
/* Implémentation standard pour l'accessibilité */
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
clip-path: inset(50%);
white-space: nowrap;
border-width: 0;
}
<section>
<h2 class="visually-hidden">Produits phares</h2>

<div class="product-grid">...</div>
</section>

<section>
<h2 class="visually-hidden">Témoignages clients</h2>

<div class="testimonials">...</div>
</section>

<section>
<h2 class="visually-hidden">Contactez-nous</h2>

<form>...</form>
</section>

Objectif : Créer un lien de saut vers le contenu principal qui n'est visible qu'au focus.

Consigne :

  1. Créez un lien <a> avec l'id skip-main.
  2. Utilisez la classe .visually-hidden pour le masquer initialement.
  3. Ajoutez une règle CSS pour que, lors du :focus, le lien devienne visible en haut de la page avec un fond noir et un texte blanc.
Découvrir la solution commentée
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
clip-path: inset(50%);
white-space: nowrap;
border-width: 0;
}

/* On surcharge le masquage au focus */
.skip-link:focus {
position: fixed;
top: 10px;
left: 10px;
width: auto;
height: auto;
padding: 1rem;
background-color: #000;
color: #fff;
z-index: 1000;
clip: auto;
clip-path: none;
border-radius: 4px;
}
<a href="#main-content" class="visually-hidden skip-link">
Aller directement au contenu principal
</a>

<nav>

</nav>

<main id="main-content">
<h1>Bienvenue sur le site</h1>
</main>

Exercice 3 : Conception (Formulaire accessible)

Objectif : Concevoir un formulaire de recherche "minimaliste" où les labels sont masqués mais l'accessibilité est parfaite.

Consigne :

  1. Créez un champ de recherche avec un input et un button.
  2. Le label du champ doit être masqué visuellement.
  3. Le bouton ne doit contenir qu'une icône (simulée par un caractère ou un SVG), mais doit posséder un texte descriptif pour les lecteurs d'écran.
  4. Assurez-vous qu'aucun élément display: none n'est utilisé pour les éléments sémantiques.
Découvrir la solution commentée
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
clip-path: inset(50%);
white-space: nowrap;
border-width: 0;
}

.search-container {
display: flex;
gap: 0;
border: 1px solid #ccc;
border-radius: 20px;
padding: 5px;
width: fit-content;
}

.search-input {
border: none;
outline: none;
padding: 5px 10px;
}

.search-submit {
background: #007bff;
color: white;
border: none;
border-radius: 50%;
width: 30px;
height: 30px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
<form class="search-container">

<label for="site-search" class="sr-only">Rechercher un produit</label>

<input type="text" id="site-search" class="search-input" placeholder="Rechercher...">

<button type="submit" class="search-submit">

<span aria-hidden="true">🔍</span>

<span class="sr-only">Lancer la recherche</span>
</button>
</form>