Comprendre comment le navigateur calcule la priorité des sélecteurs et comment résoudre les conflits de styles.
La spécificité est comme un score : plus le score est élevé, plus la règle a de chances de s'appliquer. C'est le mécanisme principal de la cascade après l'origine des styles.
Hiérarchie de la spécificité (du plus faible au plus fort) : ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Priorité croissante → * (sélecteur universel) → 0, 0, 0 p (sélecteur d'élément) → 0, 0, 1 p span (deux éléments) → 0, 0, 2 .classe (sélecteur de classe) → 0, 1, 0 .a .b (deux classes) → 0, 2, 0 #id (sélecteur d'ID) → 1, 0, 0 #a #b (deux IDs) → 2, 0, 0 style="" (CSS inline) → 1, 0, 0, 0 !important (importance) → SURPASSE TOUT
La hiérarchie des sélecteurs détermine leur poids respectif
(a, b, c, d)
| Position | Nom | Ce qui compte |
|---|---|---|
a |
Inline styles | Nombre de style="" (0 ou 1 en pratique) |
b |
IDs | Nombre de sélecteurs ID (#id) |
c |
Classes, pseudo-classes, attributs | Nombre de .classe, :hover, [attr] |
d |
Éléments, pseudo-éléments | Nombre de p, div, ::before |
(a×1000 + b×100 + c×10 + d). Par exemple, (0, 1, 0, 0) = 100, (0, 0, 2, 1) = 21.
/* Sélecteur 1 : #menu */
/* # = 1 ID, 0 classes, 0 éléments */
/* Score : (0, 1, 0, 0) → 100 */
/* Sélecteur 2 : .nav .item a */
/* 0 IDs, 2 classes, 1 élément */
/* Score : (0, 0, 2, 1) → 21 */
/* Sélecteur 3 : div p span */
/* 0 IDs, 0 classes, 3 éléments */
/* Score : (0, 0, 0, 3) → 3 */
/* Sélecteur 4 : * */
/* 0 IDs, 0 classes, 0 éléments */
/* Score : (0, 0, 0, 0) → 0 */
:hover, :first-child, :nth-child()) et les sélecteurs d'attributs ([type="text"]) ont le même poids qu'une classe : (0, 0, 1, 0).
/* :hover → compte comme 1 classe */
a:hover { color: red; }
/* Score : (0, 0, 1, 1) = 11 — 1 pseudo-classe + 1 élément */
/* :first-child → compte comme 1 classe */
p:first-child { font-weight: bold; }
/* Score : (0, 0, 1, 1) = 11 */
/* [type="text"] → compte comme 1 classe */
input[type="text"] { border: 1px solid; }
/* Score : (0, 0, 1, 1) = 11 */
/* :nth-child(2n) → compte comme 1 classe */
li:nth-child(2n) { background: #f5f5f5; }
/* Score : (0, 0, 1, 1) = 11 */
:is(), :where() et :has()| Pseudo-classe | Spécificité | Description |
|---|---|---|
:is() |
Prend la spécificité du plus spécifique argument | :is(#id, .classe) → spécificité de #id |
:where() |
Toujours (0, 0, 0, 0) = 0 | Permet de créer des règles facilement surchargeables |
:has() |
Prend la spécificité du plus spécifique argument | .a:has(#b) → spécificité de #b + .a |
/* :is() prend la spécificité du plus spécifique */
:is(.a, .b, .c) { color: red; }
/* Score : (0, 0, 1, 0) — 1 classe */
:is(#header, .nav) { color: blue; }
/* Score : (0, 1, 0, 0) — 1 ID (le plus spécifique) */
/* :where() a toujours spécificité 0 */
:where(#header, .nav) { color: green; }
/* Score : (0, 0, 0, 0) — zéro ! */
/* :has() prend la spécificité du plus spécifique */
.card:has(.featured) { border: 2px solid gold; }
/* Score : (0, 0, 2, 0) — 2 classes */
.card:has(#special) { border: 2px solid red; }
/* Score : (0, 1, 1, 0) — 1 ID + 1 classe */
/* Les pseudo-éléments comptent comme 1 élément */
p::before { content: "→ "; }
/* Score : (0, 0, 0, 2) — 1 élément + 1 pseudo-élément */
a::after { content: " ↗"; }
/* Score : (0, 0, 0, 2) */
Calcul de spécificité — exemples : ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Sélecteur IDs Cls Élm Score Total ────────────────────────────────────────────────────────── * 0 0 0 0000 0 p 0 0 1 0001 1 p span 0 0 2 0002 2 .classe 0 1 0 0010 10 .a .b 0 2 0 0020 20 .a .b .c 0 3 0 0030 30 #id 1 0 0 0100 100 #a #b 2 0 0 0200 200 #header .nav a 0 1 1 0011 11 #header .nav a:hover 0 2 1 0021 21 #header .nav a::after 0 1 2 0012 12 [type="text"] 0 1 0 0010 10 :is(#id, .class) 1 0 0 0100 100 :where(.a, .b) 0 0 0 0000 0 .a:has(#b) 1 1 0 0110 110 div.card:hover::before 0 1 2 0012 12 [href^="http"]:not(.internal) 0 2 0 0020 20
Le score est lu colonne par colonne : un ID (100) bat toujours n'importe quel nombre de classes (10 chacune)
Comparaison : 99 classes vs 1 ID ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ .a .b .c .d .e .f .g .h .i .j #seul .k .l .m .n .o .p .q .r .s .t id .u .v .w .x .y .z .aa .bb .cc .dd .ee .ff .gg .hh .ii .jj .kk .ll .mm .nn .oo .pp .qq .rr .ss .tt .uu .vv .ww .xx .yy .zz .aaa .bbb .ccc .ddd .eee .fff .ggg .hhh .iii .jjj .kkk .lll .mmm .nnn .ooo .ppp .qqq .rrr .sss .ttt .uuu .vvv Score : (0,0,99,0) Score : (0,1,0,0) = 990 = 1000 990 < 1000 → L'ID GAGNE ! ✓ Les positions ne se mélangent PAS. 1 ID > N classes (quel que soit N)
La spécificité est un système positionnel, pas un simple total
Démonstration : p vs h1
p { color: blue; } /* (0,0,0,1) = 1 */
h1 { color: red; } /* (0,0,0,1) = 1 */
/* Même score → l'ordre source gagne */
/* Si h1 vient en dernier → rouge */
Paragraphe bleu — p est en dernier dans le code CSS
Démonstration : .texte-rouge vs p
p { color: blue; } /* (0,0,0,1) = 1 */
.texte-rouge { color: red; } /* (0,0,1,0) = 10 */
/* La classe gagne toujours sur l'élément */
Ce paragraphe est rouge — la classe gagne sur l'élément p
Démonstration : #titre vs .titre
#titre { color: red; } /* (0,1,0,0) = 100 */
.titre { color: blue; } /* (0,0,1,0) = 10 */
/* L'ID gagne toujours sur la classe */
Démonstration : .a .b vs .c
.a .b { color: red; } /* (0,0,2,0) = 20 — deux classes */
.c { color: blue; } /* (0,0,1,0) = 10 — une seule classe */
/* Deux classes battent une seule classe */
Démonstration : #a.b vs #a
#a.b { color: red; } /* (0,1,1,0) = 110 — 1 ID + 1 classe */
#a { color: blue; } /* (0,1,0,0) = 100 — 1 ID seul */
/* 1 ID + 1 classe > 1 ID seul */
Démonstration : :hover compte comme 1 classe
a { color: blue; } /* (0,0,0,1) = 1 */
a:hover { color: red; } /* (0,0,1,1) = 11 */
/* Survoler le lien → il devient rouge */
Démonstration : La différence cruciale
/* :is() prend la spécificité du plus spécifique */
:is(#id, .classe) { color: red; }
/* → Score : (0,1,0,0) = 100 (l'ID domine) */
/* :where() a toujours spécificité 0 */
:where(#id, .classe) { color: blue; }
/* → Score : (0,0,0,0) = 0 */
/* Résultat : .classe seule peut battre :where(#id, .classe)
mais ne peut PAS battre :is(#id, .classe) */
Démonstration : .card:has(#special)
.card:has(#special) { border: 3px solid red; }
/* Score : (0,1,1,0) = 110 — 1 ID de #special + 1 classe de .card */
.card { border: 1px solid gray; }
/* Score : (0,0,1,0) = 10 */
/* Le :has() avec un ID rend la spécificité très élevée */
Démonstration : Multi-sélecteurs complexes
/* Sélecteur A */
#sidebar .nav li.active a:hover {
color: red;
}
/* IDs : 1, Classes : 3 (.nav, .active, :hover), Éléments : 2 (li, a) */
/* Score : (0, 1, 3, 2) = 132 */
/* Sélecteur B */
#sidebar .nav li a {
color: blue;
}
/* IDs : 1, Classes : 1, Éléments : 3 */
/* Score : (0, 1, 1, 3) = 113 */
/* 132 > 113 → A gagne quand on survole */
/* FAUX raisonnement : "10 classes = 1 ID car 10×10 = 100" */
.a .b .c .d .e .f .g .h .i .j { color: red; }
/* Score : (0,10,0,0) → dans le calcul réel = 100 */
/* MAIS la comparaison se fait colonne par colonne ! */
#seul { color: blue; }
/* Score : (0,1,0,0) → 1 ID */
/* Comparaison colonne par colonne :
0 == 0 (a)
10 > 1 (b) → .a...j gagne ?
En fait, non ! En CSS, on compare b, c, d séparément.
10 classes vs 1 ID : l'ID gagne car on compare 1 > 0 en position b.
Les 10 classes ne « remplissent » pas la position de l'ID.
EN RÉALITÉ : 1 ID bat N classes, peu importe N. */
:is() et :where()
/* FAUX : croire que :is() et :where() ont la même spécificité */
:is(#id, .classe) { color: red; } /* (0,1,0,0) = 100 */
:where(#id, .classe) { color: blue; } /* (0,0,0,0) = 0 */
/* :is() prend la spécificité du plus spécifique argument */
/* :where() a TOUJOURS spécificité 0 */
/* FAUX : * n'ajoute rien à la spécificité */
* { color: red; } /* (0,0,0,0) */
* * * * * { color: blue; } /* toujours (0,0,0,0) */
/* Le sélecteur universel a une spécificité de 0 quoi qu'il arrive */
/* FAUX : croire que p.class est la même chose que .class p */
p.class { color: red; } /* (0,0,1,1) = 11 — 1 classe + 1 élément */
.class p { color: blue; } /* (0,0,1,1) = 11 — 1 classe + 1 élément */
/* Même score, même résultat dans ce cas ! */
/* Mais attention : */
p.class { color: red; } /* cible les p AVEC .class */
.class p { color: blue; } /* cible les p DANS .class — ce sont des règles différentes */
/* FAUX : ajouter un ID pour écraser un style */
#page #main #content #article #texte p { color: red; }
/* Score : (0,4,0,1) = 401 — spécificité énorme, impossible à surcharger */
/* CORRECT : utiliser une classe ciblée */
.article-texte { color: red; }
/* Score : (0,0,1,0) = 10 — facile à surcharger si nécessaire */
:where() pour des styles facilement surchargeables (thèmes, defaults).:is() pour écrire des sélecteurs concis sans perte de spécificité./* ✅ Bonne pratique : spécificité constante et basse */
.card {} /* (0,0,1,0) = 10 */
.card__title {} /* (0,0,2,0) = 20 */
.card__title--featured {} /* (0,0,3,0) = 30 */
/* ✅ Bonne pratique : :where() pour des defaults surchargeables */
:where(.theme-dark) { color: white; } /* (0,0,0,0) */
/* ✅ Bonne pratique : :is() pour des listes de sélecteurs */
:is(h1, h2, h3) { font-weight: bold; }
Exercice 1 : Calcul basique
Calcule la spécificité de chaque sélecteur :
p { } /* → (0,0,0,1) = 1 */
.classe { } /* → (0,0,1,0) = 10 */
#id { } /* → (0,1,0,0) = 100 */
* { } /* → (0,0,0,0) = 0 */
Exercice 2 : Sélecteurs composés
/* Calcule la spécificité : */
nav .menu a { } /* → (0,0,1,2) = 12 — 1 classe + 2 éléments */
#header .nav li a { } /* → (0,1,1,3) = 113 — 1 ID + 1 classe + 3 éléments */
.container .a .b .c { } /* → (0,0,3,0) = 30 — 3 classes */
div#main { } /* → (0,1,0,1) = 101 — 1 ID + 1 élément */
p::before { } /* → (0,0,0,2) = 2 — 1 élément + 1 pseudo-élément */
Exercice 3 : Quel gagne ?
Pour chaque paire, indique quel sélecteur gagne :
/* Paire 1 */
.texte { color: red; } /* 10 */
p { color: blue; } /* 1 */
/* → .texte gagne (10 > 1) */
/* Paire 2 */
#titre { color: red; } /* 100 */
.titre { color: blue; } /* 10 */
/* → #titre gagne (100 > 10) */
/* Paire 3 */
.a .b .c { color: red; } /* 30 */
#seul { color: blue; } /* 100 */
/* → #seul gagne (100 > 30) */
/* Paire 4 */
nav .link:hover { color: red; } /* 21 */
nav .link { color: blue; } /* 11 */
/* → nav .link:hover gagne (21 > 11) */
/* Paire 5 */
:where(#id) { color: red; } /* 0 */
.classe { color: blue; } /* 10 */
/* → .classe gagne (10 > 0) — :where() a spécificité 0 */
Exercice 4 : :is() vs :where()
/* Calcule la spécificité : */
:is(#header, .nav) { } /* → (0,1,0,0) = 100 — l'ID domine */
:where(#header, .nav) { } /* → (0,0,0,0) = 0 — toujours 0 */
:is(.a, .b, .c) { } /* → (0,0,1,0) = 10 — la classe domine */
:has(.featured) { } /* → (0,0,1,0) = 10 */
.card:has(#special) { } /* → (0,1,1,0) = 110 — ID + classe */
Projet : Calculateur de spécificité
Crée un outil HTML/CSS/JS qui calcule la spécificité d'un sélecteur :
Fonctionnalités :
Structure suggérée :
<div class="calculette">
<input type="text" id="selecteur" placeholder="Entrez un sélecteur...">
<div class="resultat">
<span class="score">(0, 0, 0, 0)</span>
<div class="decomposition">
IDs: 0 | Classes: 0 | Éléments: 0
</div>
</div>
</div>
Quiz rapide
.ma-classe ?#mon-id ?#id bat-il .a .b .c .d .e (5 classes) ?style="" ?:is(#a, .b) a quelle spécificité ?:hover, :nth-child()) ont le même poids qu'une classe ou un élément ?#header .nav a:hover ?initial et la valeur par défaut du navigateur ?:where()initial = valeur initiale dans le spéculaire CSS. La valeur par défaut du navigateur peut être différente (user-agent stylesheet).