Cibler des états, des positions et des conditions spéciales d'un élément sans modifier le HTML.
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;
}
| 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 |
: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;
}
: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;
}
: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;
}
: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;
}
/* Élément unique (pas de frères) */
li:only-child {
font-weight: 700;
padding: 1rem;
}
/* Élément vide (aucun enfant) */
div:empty {
display: none;
}
: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;
}
:target: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;
}
┌──────────────────────────────────────────────────────┐ │ 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
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);
}
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);
}
Démonstration : Coche les cases !
.check-item input:checked + label {
color: #2563eb;
font-weight: 600;
text-decoration: line-through;
}
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;
}
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;
}
Démonstration : Éléments spéciaux
.not-list li:not(.special) {
background: #f1f5f9;
}
.not-list li.special {
background: #f59e0b;
color: #78350f;
font-weight: 600;
}
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;
}
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);
}
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;
}
Premier paragraphe — ciblé par :first-of-type (gras bleu)
Deuxième paragraphe — pas ciblé
Troisième paragraphe — ciblé par :last-of-type (italique violet)
:: 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; }
/* 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;
}
/* 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;
}
/* FAUX — trop complexe et lent */
*:has(> div > p > span > a) { ... }
/* CORRECT — garder :has() simple */
.card:has(img) { ... }
.form:has(:invalid) { ... }
:active avec :hover pour la compatibilité mobile.:focus-visible au lieu de :focus pour l'accessibilité sans polluer l'interface au clic.:nth-of-type() à :nth-child() quand il y a des éléments mixtes.:not() pour les exceptions plutôt que de créer des classes spéciales.:has() avec un fallback pour les navigateurs anciens.: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;
}
Exercice 1 : Hover et transition
Crée un bouton avec :
#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 :
#f1f5f9#dbeafe4px solid #7c3aed#fef3c7Solution :
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 :
:focus-visible:invalid:not(:placeholder-shown):valid:focus-withinSolution :
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 :
<p> sauf ceux avec la classe .skip.card qui contiennent une imageSolution :
p:not(.skip) {
line-height: 1.8;
}
.card:has(img) {
display: grid;
grid-template-columns: 200px 1fr;
}
Projet : Liste interactive avec états
Crée une page avec :
<ul> avec <li>)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 contextuelQuiz rapide
:focus et :focus-visible ?li:nth-child(odd) cible les éléments impairs ou pairs ?:not() fait ?:active avec :hover ?:is() et :where() ?:nth-child(3n) signifie quoi exactement ?::hover:focus s'applique à tout focus ; :focus-visible uniquement au focus clavier:first-of-type:has():is() prend la spécificité du sélecteur le plus élevé ; :where() a une spécificité de 0