Pseudo-classes et pseudo-éléments CSS : de :hover à ::before

Un lien change de couleur quand la souris passe dessus. Un petit carré apparaît devant son texte, sans balise supplémentaire. Les deux effets s'écrivent en CSS, mais ils ne font pas la même chose : une pseudo-classe sélectionne un élément sous une condition ; un pseudo-élément désigne une partie de son rendu.

Nous allons les réunir dans une petite page, puis vérifier ce qui change au survol, au clavier et quand on désactive le décor. Pour reprendre la syntaxe d'une règle, retrouvez d'abord comment donner du style à une page avec CSS.

Un deux-points teste, deux deux-points désignent une partie

Dans .suite:hover, .suite sélectionne les éléments qui portent cette classe HTML et :hover ajoute une condition de survol. Le lien reste le même élément. Toutes les pseudo-classes ne décrivent pas une interaction : :first-child teste une position parmi les enfants d'un parent.

Dans .suite::before, nous visons le pseudo-élément placé avant le contenu du lien. ::after se place après. Ces deux pseudo-éléments permettent ici de produire des boîtes décoratives. D'autres ciblent une partie du texte, comme ::first-letter : « pseudo-élément » ne signifie donc pas toujours « contenu ajouté ».

.suite:hoverLe lien est-il survolé ?Rendu de .suite ::before::after texte HTML
Une condition sur l'élément d'un côté ; des parties de son rendu de l'autre.

Retenez l'écriture moderne : un : pour une pseudo-classe, deux :: pour un pseudo-élément. L'ancienne écriture :before est encore acceptée pour compatibilité, mais choisissez ::before dans votre nouveau code.

Monter une page que l'on peut vraiment manipuler

Créez deux fichiers dans le même dossier : index.html et style.css. Copiez ce premier bloc dans le fichier HTML. La destination du lien est une section de la même page ; aucune requête vers un service n'est nécessaire.

<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Tester les pseudo-classes et pseudo-éléments</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main>
    <h1>Un lien, deux décors</h1>
    <a class="suite" href="#programme">Voir le programme</a>
    <p class="option">
      <input id="rappel" type="checkbox">
      <label for="rappel">Ajouter un rappel</label>
    </p>
    <section id="programme">
      <h2>Programme</h2>
      <ul class="etapes">
        <li>Préparer le HTML</li>
        <li>Écrire le CSS</li>
        <li>Tester au clavier</li>
      </ul>
    </section>
  </main>
</body>
</html>

Placez ensuite tout ce CSS dans style.css et ouvrez index.html dans votre navigateur.

body {
  margin: 0;
  padding: 2rem 1rem;
  font: 1rem/1.6 system-ui, sans-serif;
  color: #172033;
  background: #ffffff;
}
main { max-width: 40rem; margin: auto; }
.suite {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  color: #172033;
  background: #ffffff;
  border: 2px solid currentColor;
  border-radius: 0.4rem;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}
.suite:hover { background: #e0e7ff; }
.suite:focus-visible {
  outline: 3px solid #4338ca;
  outline-offset: 4px;
}
.suite::before {
  content: "";
  width: 0.6rem;
  height: 0.6rem;
  background: currentColor;
}
.suite::after {
  content: "";
  width: 0.45rem;
  height: 0.45rem;
  border-top: 2px solid currentColor;
  border-right: 2px solid currentColor;
  transform: rotate(45deg);
}
.suite:hover::before { background: #4338ca; }
.etapes li:first-child { font-weight: 700; }
.etapes li:nth-child(2) { text-decoration: underline; }
#rappel:checked + label { font-weight: 700; }

Faire le trajet souris, puis le trajet clavier

Survolez le lien : son fond devient bleu clair et son carré devient indigo. Puis déplacez la souris dans une zone vide, rechargez la page et appuyez sur Tab. Le lien reçoit le focus et un contour indigo apparaît, sans devoir le survoler. Avec Entrée, vous suivez son ancre.

:focus désigne l'élément qui a le focus. :focus-visible s'applique lorsque le navigateur estime qu'un indicateur de focus doit être montré, notamment lors de la navigation au clavier. Ce n'est pas un synonyme strict de « clavier » : les critères appartiennent au navigateur. Ici, nous personnalisons le contour de focus avec :focus-visible, tout en conservant un indicateur visible.

Le survol ne doit pas être la seule façon de découvrir une information ou d'utiliser une action : le tactile ne fournit pas toujours un survol utilisable. Dans cette page, le lien et son libellé existent dès le départ ; les styles indiquent l'interaction sans conditionner l'accès au programme.

Le 16 octobre 2026, le banc a relevé ces états dans Chromium. hover, focus et focusVisible correspondent aux résultats des sélecteurs sur le lien ; le contour est lu dans son style calculé.

repos : hover=false focus=false focusVisible=false
survol : hover=true fond=rgb(224, 231, 255) carre=rgb(67, 56, 202)
clavier : hover=false focus=true focusVisible=true contour=solid/3px

Le piège du carré absent : content n'est pas facultatif

Nos décors utilisent content: "", une chaîne vide. Elle suffit pour générer les boîtes auxquelles nous donnons des dimensions, un fond ou des bordures. Avec content: none, ::before ne génère plus sa boîte, même si les dimensions et le fond restent déclarés. Le carré disparaît.

Pour le vérifier, ajoutez temporairement cette règle à la fin de style.css, observez le lien, puis retirez-la :

.suite::before { content: none; }

Dans le banc, le lien mesure 194,921875 px avec les deux décors, puis 177,328125 px sans le carré. L'écart de 17,59375 px comprend le carré de 9,59375 px et un espacement de 8 px. Ces fractions viennent du rendu observé avec une taille de police de base de 16 px ; la largeur du texte dépend de la police et de l'environnement, ce n'est pas une constante CSS à recopier.

Ni le carré ni le chevron ne sont des éléments HTML ajoutés à l'arbre DOM : dans les deux cas, le lien conserve zéro enfant élément et son texte reste « Voir le programme ». Le CSS produit le décor autour de ce texte. Vous pouvez combiner les deux notions, comme dans .suite:hover::before : sélectionner le lien survolé, puis son décor avant.

Gardez le contenu essentiel dans le HTML : un prix, un avertissement, un libellé de bouton ou l'indication « obligatoire » ne doit pas dépendre de content. Ici, retirer le CSS laisse le lien compréhensible. Ce choix évite de faire dépendre son sens du rendu du contenu généré et de son exposition aux aides techniques ; ce banc ne teste pas les lecteurs d'écran.

Les conditions peuvent aussi suivre la structure du HTML

Dans notre liste, li:first-child met en gras le premier élément li s'il est le premier enfant de son parent. li:nth-child(2) souligne un li placé en deuxième position parmi les enfants éléments. On compte les éléments frères, pas seulement ceux qui portent le même nom de balise : un autre élément placé avant changerait les positions. Les espaces et les retours à la ligne du HTML ne comptent pas comme des éléments.

La case illustre un état différent : :checked correspond à son état coché. Dans #rappel:checked + label, le + sélectionne le label qui suit immédiatement la case parmi les éléments frères. Le texte de l'étiquette passe en gras quand on coche la case. Cela ne coche rien par CSS : la règle observe l'état d'un vrai contrôle HTML.

Pour construire des champs avec leurs étiquettes, reprenez le guide des formulaires HTML. Ajouter des pseudo-classes ne remplace pas les associations entre les champs et leurs labels.

liste : premier=700 deuxieme=underline
case : avant=400 cochee=700 decochee=400
decors : avant="" apres="" enfantsElements=0
sans-before : content=none enfantsElements=0 texte=Voir le programme

Refaire la vérification, et savoir où elle s'arrête

Pour reproduire manuellement : ouvrez les deux fichiers, comparez repos et survol, puis rechargez hors du lien et utilisez Tab. Cochez et décochez la case. Ajoutez enfin la règle content: none et comparez le carré et le texte. Les extraits ci-dessus ont été extraits directement de cet article et assemblés sans réécriture dans le banc de mesure.

Environnement du relevé : Windows 11, Node.js 20.19.4, Playwright 1.63.0 et Chromium 153.0.8010.12. Commande du banc : node mesures.mjs. Les sorties text sont citées telles que produites par ce script. Les observations DOM et les captures sont archivées ; le mode node mesures.mjs --depuis-archives vérifie leur intégrité et recalcule les sorties, sans prétendre relancer un navigateur. Ce test porte sur ce moteur et cette page, sans mesure sur appareil tactile réel ni lecteur d'écran.

Le contrôle confirme aussi que supprimer les deux décors ne change ni le texte du lien ni sa destination. C'est mon critère pratique pour ce cas : le décor peut disparaître, l'action reste compréhensible.

Références consultées le 16 octobre 2026