Niveau 2 — Sélecteurs

Les pseudo-classes

Cibler des états, des positions et des conditions spéciales d'un élément sans modifier le HTML.

1. Comprendre les pseudo-classes

En une phrase : Une pseudo-classe est un sélecteur qui cible un élément dans un état particulier (survol, focus, coché…) ou une position spécifique (premier enfant, nth…), sans nécessiter de classe HTML supplémentaire.

Les pseudo-classes commencent par un deux-points : et s'écrivent après le sélecteur auquel elles s'ajoutent. Elles ne nécessitent aucune modification du HTML — c'est leur force.

/* Syntaxe générale */
sélecteur:pseudo-classe {
  propriété: valeur;
}

/* Exemple */
a:hover {
  color: red;
}

li:first-child {
  font-weight: 700;
}

Les grandes catégories

Catégorie Pseudo-classes Cible
Interaction :hover, :active, :focus, :focus-visible, :focus-within État de l'utilisateur avec l'élément
Formulaire :checked, :disabled, :enabled, :required, :optional, :valid, :invalid État des champs de formulaire
Position :first-child, :last-child, :nth-child(), :nth-last-child() Position par rapport aux frères
Type :first-of-type, :last-of-type, :nth-of-type() Position par rapport aux frères du même type
Structurel :only-child, :empty Structure de l'arbre DOM
Négation / Logique :not(), :is(), :where(), :has() Logique conditionnelle
URL :target Fragment URL actif

2. Syntaxe détaillée

2.1 Les pseudo-classes d'interaction

Définition : Elles ciblent l'état interactif d'un élément lorsqu'un utilisateur interagit avec (survol, clic, focus clavier).

:hover — Survol de la souris

/* L'élément quand la souris passe dessus */
.btn:hover {
  background: #1d4ed8;
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}

a:hover {
  text-decoration: underline;
}

:active — Clic en cours

/* L'élément pendant qu'on maintient le clic */
.btn:active {
  transform: scale(0.95);
  background: #1e40af;
}

:focus — Focus clavier

/* L'élément qui reçoit le focus (tabulation ou clic) */
input:focus {
  border-color: #2563eb;
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
}

:focus-visible — Focus visible uniquement

/* Focus uniquement par clavier (pas par clic) */
button:focus-visible {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
}

:focus-within — Parent contenant un focus

/* Le conteneur change quand un de ses enfants a le focus */
.form-group:focus-within {
  background: #eff6ff;
  border-color: #2563eb;
}

2.2 Les pseudo-classes de formulaire

Définition : Elles ciblent l'état des champs de formulaire : cochés, désactivés, requis, valides, invalides…

:checked — Élément coché

/* Case cochée */
input[type="checkbox"]:checked {
  accent-color: #2563eb;
}

/* L'étiquette du champ coché */
input:checked + label {
  color: #2563eb;
  font-weight: 600;
}

:disabled et :enabled

/* Champ désactivé */
input:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  background: #f1f5f9;
}

/* Champ activé */
input:enabled {
  border-color: #e2e8f0;
}

:required et :optional

/* Champ obligatoire */
input:required {
  border-left: 3px solid #dc2626;
}

/* Champ optionnel */
input:optional {
  border-left: 3px solid #16a34a;
}

:valid et :invalid

/* Champ valide (respecte l'attribut pattern, type, etc.) */
input:valid {
  border-color: #16a34a;
}

/* Champ invalide */
input:invalid:not(:placeholder-shown) {
  border-color: #dc2626;
}

2.3 Les pseudo-classes de position

Définition : Elles ciblent un élément en fonction de sa position parmi ses frères (même parent). Attention : elles comptent tous les nœuds, y compris les textes et commentaires.

:first-child et :last-child

/* Premier enfant */
li:first-child {
  font-weight: 700;
  color: #2563eb;
}

/* Dernier enfant */
li:last-child {
  border-bottom: none;
}

:nth-child(an+b)

/* Impairs */
li:nth-child(odd) {
  background: #f1f5f9;
}

/* Pairs */
li:nth-child(even) {
  background: #dbeafe;
}

/* Chaque 3ème élément */
li:nth-child(3n) {
  border-left: 4px solid #7c3aed;
  font-weight: 600;
}

/* Le 5ème élément */
li:nth-child(5) {
  background: #fef3c7;
}

/* À partir du 3ème */
li:nth-child(n+3) {
  padding-left: 2rem;
}

/* Les 5 premiers */
li:nth-child(-n+5) {
  font-weight: 600;
}

:nth-last-child(an+b)

/* Compte depuis la fin */
li:nth-last-child(2) {
  color: #dc2626;
}

2.4 Les pseudo-classes de type

Définition : Contrairement à :nth-child(), elles ne comptent que les éléments du même type de balise. Utile quand il y a des éléments mixtes.
/* Premier <p> dans son conteneur (ignore les h1, div, etc.) */
p:first-of-type {
  font-weight: 700;
  color: #2563eb;
}

/* Dernier <p> dans son conteneur */
p:last-of-type {
  font-style: italic;
}

/* 2ème paragraphe */
p:nth-of-type(2) {
  background: #eff6ff;
}

2.5 Les pseudo-classes structurelles

/* Élément unique (pas de frères) */
li:only-child {
  font-weight: 700;
  padding: 1rem;
}

/* Élément vide (aucun enfant) */
div:empty {
  display: none;
}

2.6 Les pseudo-classes logiques

Définition : Elles permettent la logique conditionnelle dans les sélecteurs : négation, regroupement sans spécificité, et sélection par le contenu fils.

:not(sélecteur) — Négation

/* Tous les éléments SAUF ceux avec .ignore */
p:not(.ignore) {
  line-height: 1.8;
}

/* Tous les liens SAUF ceux vers l'extérieur */
a:not([href^="http"]) {
  color: #7c3aed;
}

:is(sélecteur1, sélecteur2) — Regroupement court

/* Au lieu de écrire : h1, h2, h3, h4, h5, h6 */
:is(h1, h2, h3, h4, h5, h6) {
  line-height: 1.3;
  margin-bottom: 0.5rem;
}

/* Spécificité : prend la plus élevée des sélecteurs internes */

:where(sélecteur1, sélecteur2) — Comme :is() mais 0 de spécificité

/* Utile pour les styles par défaut faciles à surcharger */
:where(h1, h2, h3) {
  line-height: 1.3;
}

/* Spécificité = 0, facile à surcharger */

:has(sélecteur) — Le « sélecteur parent »

/* La carte qui contient une image */
.card:has(img) {
  display: grid;
  grid-template-columns: 200px 1fr;
}

/* Le formulaire qui a au moins un champ invalide */
.form:has(:invalid) {
  border-color: #dc2626;
}

/* Le conteneur qui contient un lien actif */
nav:has(.active) {
  background: #eff6ff;
}

2.7 La pseudo-classe :target

Définition : :target cible l'élément dont l'ID correspond à l'ancre active dans l'URL (ex: page.html#section-1).
/* Style de l'élément ciblé par l'URL */
:target {
  background: #eff6ff;
  border-left: 4px solid #2563eb;
  padding: 1rem;
}

3. Visualisation

┌──────────────────────────────────────────────────────┐
│           PSEUDO-CLASSES PAR CATÉGORIE              │
├──────────────────────────────────────────────────────┤
│                                                      │
│  🖱️ INTERACTION                                      │
│  :hover → souris dessus                              │
│  :active → clic en cours                             │
│  :focus → focus clavier                              │
│  :focus-visible → focus clavier uniquement            │
│  :focus-within → enfant a le focus                   │
│                                                      │
│  📋 FORMULAIRE                                        │
│  :checked → coché         :required → obligatoire    │
│  :disabled → désactivé    :optional → non obligatoire│
│  :enabled → activé        :valid → valide            │
│                            :invalid → invalide        │
│                                                      │
│  📍 POSITION                                          │
│  :first-child → 1er      :nth-child(3n) → chaque 3  │
│  :last-child → dernier   :nth-child(odd) → impairs   │
│  :nth-child(even) → pairs                             │
│                                                      │
│  🏷️ TYPE                                              │
│  :first-of-type → 1er du même type                   │
│  :last-of-type → dernier du même type                │
│                                                      │
│  🧩 LOGIQUE                                           │
│  :not() → sauf                                      
│  :is() → ou (avec spécificité)                       
│  :where() → ou (sans spécificité)                    
│  :has() → qui contient                               
└──────────────────────────────────────────────────────┘

Carte mentale des pseudo-classes

4. Exemples interactifs

4.1 Survol et clic

Démonstration : Passe la souris et clique !

.hover-box {
  border: 2px solid #e2e8f0;
  border-radius: 8px;
  text-align: center;
  cursor: pointer;
  transition: all 0.3s ease;
}

.hover-box:hover {
  background: #2563eb;
  color: white;
  border-color: #2563eb;
  transform: translateY(-3px);
  box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1);
}

.hover-box:active {
  transform: scale(0.95);
}
Survole-moi !
Clique sur moi !
Essaie les deux !

4.2 Focus et focus-visible

Démonstration : Utilise la touche Tab !

.focus-input:focus {
  border-color: #2563eb;
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
}

.focus-input:focus-visible {
  border-color: #7c3aed;
  box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.3);
}

4.3 Cases cochées

Démonstration : Coche les cases !

.check-item input:checked + label {
  color: #2563eb;
  font-weight: 600;
  text-decoration: line-through;
}

4.4 États disabled / enabled

Démonstration : Boutons activés et désactivés

.form-btn:enabled {
  background: #2563eb;
  color: white;
}

.form-btn:disabled {
  background: #e2e8f0;
  color: #94a3b8;
  cursor: not-allowed;
  opacity: 0.6;
}

4.5 nth-child

Démonstration : Listes avec alternating colors

.nth-list li:nth-child(odd) {
  background: #dbeafe;
}

.nth-list li:nth-child(3n) {
  border-left: 4px solid #7c3aed;
  font-weight: 600;
}

.nth-list li:first-child {
  background: #2563eb;
  color: white;
}

.nth-list li:last-child {
  background: #f59e0b;
  color: #78350f;
}
  • 1er élément — first-child (bleu)
  • 2ème élément — impair (bleu clair)
  • 3ème élément — 3n (violet)
  • 4ème élément — pair (blanc)
  • 5ème élément — impair (bleu clair)
  • 6ème élément — 3n (violet)
  • 7ème élément — impair (bleu clair)
  • 8ème élément — pair (blanc)
  • 9ème élément — 3n (violet)
  • 10ème élément — last-child (jaune)

4.6 Négation avec :not()

Démonstration : Éléments spéciaux

.not-list li:not(.special) {
  background: #f1f5f9;
}

.not-list li.special {
  background: #f59e0b;
  color: #78350f;
  font-weight: 600;
}
  • Élément normal
  • Élément spécial !
  • Élément normal
  • Élément normal
  • Un autre spécial !

4.7 Le parent intelligent : :has()

Démonstration : Le formulaire change quand tu tapes !

/* Le groupe change quand l'input a le focus */
.field-group:has(input:focus) {
  background: #eff6ff;
  border: 1px solid #2563eb;
}

/* L'input est valide */
input:valid {
  border-color: #16a34a;
}

/* L'input est invalide (sauf quand vide) */
input:invalid:not(:placeholder-shown) {
  border-color: #dc2626;
}

4.8 Ciblage par l'URL : :target

Démonstration : Clique sur les liens !

.target-section:target {
  border-color: #2563eb;
  background: #eff6ff;
  box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1);
}
Section A — Cible : #section-a
Section B — Cible : #section-b
Section C — Cible : #section-c

4.9 Premier et dernier du type

Démonstration : Différence avec :first-child

/* Premier <p> unique dans le conteneur */
p:first-of-type {
  font-weight: 700;
  color: #2563eb;
  font-size: 1.05rem;
}

/* Dernier <p> unique dans le conteneur */
p:last-of-type {
  font-style: italic;
  color: #7c3aed;
}

Titre du conteneur

Premier paragraphe — ciblé par :first-of-type (gras bleu)

Deuxième paragraphe — pas ciblé

Un div intermédiaire — ignoré par :first-of-type

Troisième paragraphe — ciblé par :last-of-type (italique violet)

5. Erreurs fréquentes

Erreur 1 : Confondre :: et :
/* FAUX — ::before est un pseudo-ÉLÉMENT, pas une pseudo-classe */
a::hover { color: red; }

/* CORRECT — :hover est une pseudo-classe */
a:hover { color: red; }
Erreur 2 : nth-child() compte tous les nœuds
/* Attention : nth-child() compte AUSSI les nœuds texte ! */
ul li:nth-child(2) {
  color: red;
}

/* Si le <ul> a des espaces vides ou des commentaires,
   le comptage peut être inattendu */

/* Solution : utiliser nth-of-type() pour cibler par type */
li:nth-of-type(2) {
  color: red;
}
Erreur 3 : :hover ne fonctionne pas sur mobile
/* Sur mobile, il n'y a pas de "survol" de souris ! */
.btn:hover {
  background: blue;
}

/* Solution : ajouter :active pour le mobile */
.btn:hover,
.btn:active {
  background: blue;
}
Erreur 4 : :has() avec des sélecteurs trop complexes
/* FAUX — trop complexe et lent */
*:has(> div > p > span > a) { ... }

/* CORRECT — garder :has() simple */
.card:has(img) { ... }
.form:has(:invalid) { ... }

6. Bonnes pratiques

Règles d'or :
  • Toujours inclure :active avec :hover pour la compatibilité mobile.
  • Utiliser :focus-visible au lieu de :focus pour l'accessibilité sans polluer l'interface au clic.
  • Préférer :nth-of-type() à :nth-child() quand il y a des éléments mixtes.
  • Utiliser :not() pour les exceptions plutôt que de créer des classes spéciales.
  • Tester :has() avec un fallback pour les navigateurs anciens.
  • Éviter les sélecteurs trop complexes dans :nth-child() — garde-les simples.
/* ✅ Bonne pratique : hover + active */
.btn:hover,
.btn:active {
  background: #1d4ed8;
}

/* ✅ Bonne pratique : focus-visible pour l'accessibilité */
a:focus-visible,
button:focus-visible,
input:focus-visible {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
}

/* ✅ Bonne pratique : nth-of-type pour les listes mixtes */
article > p:first-of-type {
  font-size: 1.1rem;
  font-weight: 500;
}

/* ✅ Bonne pratique : négation pour les styles de base */
p:not(.no-spacing) {
  margin-bottom: 1rem;
}

7. Mini-défi

Exercice 1 : Hover et transition

Crée un bouton avec :

  • État normal : fond #2563eb, texte blanc
  • :hover : fond #1d4ed8, décalage vers le haut translateY(-2px)
  • :active : scale(0.95)

Solution :

.btn {
  background: #2563eb;
  color: white;
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 8px;
  font-size: 1rem;
  cursor: pointer;
  transition: all 0.2s ease;
}

.btn:hover {
  background: #1d4ed8;
  transform: translateY(-2px);
}

.btn:active {
  transform: scale(0.95);
}

Exercice 2 : nth-child

Écris des règles pour une liste de 8 éléments :

  • Les impairs : fond #f1f5f9
  • Les pairs : fond #dbeafe
  • Le 3ème : bordure gauche 4px solid #7c3aed
  • Le dernier : fond #fef3c7

Solution :

li:nth-child(odd) {
  background: #f1f5f9;
}

li:nth-child(even) {
  background: #dbeafe;
}

li:nth-child(3) {
  border-left: 4px solid #7c3aed;
}

li:last-child {
  background: #fef3c7;
}

Exercice 3 : Formulaire accessible

Crée un champ de saisie avec :

  • Bordure grise normalement
  • Bordure bleue avec ombre au :focus-visible
  • Bordure rouge si :invalid:not(:placeholder-shown)
  • Bordure verte si :valid
  • Le groupe parent change de fond au :focus-within

Solution :

input {
  border: 2px solid #e2e8f0;
  border-radius: 6px;
  padding: 0.6rem;
  transition: all 0.2s;
}

input:focus-visible {
  border-color: #2563eb;
  box-shadow: 0 0 0 3px rgba(37,99,235,0.2);
}

input:valid {
  border-color: #16a34a;
}

input:invalid:not(:placeholder-shown) {
  border-color: #dc2626;
}

.form-group:focus-within {
  background: #eff6ff;
}

Exercice 4 : :not() et :has()

Écris :

  • Un sélecteur pour tous les <p> sauf ceux avec la classe .skip
  • Un sélecteur pour les cartes .card qui contiennent une image

Solution :

p:not(.skip) {
  line-height: 1.8;
}

.card:has(img) {
  display: grid;
  grid-template-columns: 200px 1fr;
}

8. Projet d'application

Projet : Liste interactive avec états

Crée une page avec :

  • Une liste de tâches (<ul> avec <li>)
  • Chaque tâche a une case à cocher
  • Des boutons activés et désactivés
  • Un formulaire avec validation

CSS requis :

  • :hover sur les tâches et boutons
  • :checked + label pour barrer les tâches terminées
  • :nth-child() pour alterner les couleurs
  • :disabled pour les boutons inactifs
  • :focus-visible pour l'accessibilité
  • :valid / :invalid pour la validation des champs
  • :has() pour un effet contextuel

9. Révision

Quiz rapide

  1. Quel est le symbole d'une pseudo-classe ?
  2. Quelle pseudo-clase cible l'état de survol de la souris ?
  3. Quelle est la différence entre :focus et :focus-visible ?
  4. li:nth-child(odd) cible les éléments impairs ou pairs ?
  5. Quelle pseudo-clase cible le premier enfant d'un même type de balise ?
  6. Qu'est-ce que :not() fait ?
  7. Quelle pseudo-clase permet de cibler un parent en fonction de ses enfants ?
  8. Pourquoi faut-il inclure :active avec :hover ?
  9. Quelle est la différence entre :is() et :where() ?
  10. :nth-child(3n) signifie quoi exactement ?
Réponses :
  1. Le deux-points :
  2. :hover
  3. :focus s'applique à tout focus ; :focus-visible uniquement au focus clavier
  4. Impairs (1, 3, 5…)
  5. :first-of-type
  6. Il exclut les éléments qui correspondent au sélecteur donné
  7. :has()
  8. Pour la compatibilité mobile (pas de :hover sur tactile)
  9. :is() prend la spécificité du sélecteur le plus élevé ; :where() a une spécificité de 0
  10. Cible chaque 3ème élément (3, 6, 9, 12…)