Niveau 3 — La Cascade

La spécificité

Comprendre comment le navigateur calcule la priorité des sélecteurs et comment résoudre les conflits de styles.

1. Comprendre la spécificité

En une phrase : La spécificité est un poids numérique attribué à chaque sélecteur CSS qui détermine lequel « gagne » quand plusieurs règles ciblent le même élément avec des propriétés conflictuelles.

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

2. Syntaxe détaillée

2.1 La notation (a, b, c, d)

Système de notation : Chaque sélecteur reçoit un score à 4 chiffres : (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
Astuce : On peut simplifier en scores décimaux : (a×1000 + b×100 + c×10 + d). Par exemple, (0, 1, 0, 0) = 100, (0, 0, 2, 1) = 21.

2.2 Comment calculer la spécificité

/* 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 */

2.3 Pseudo-classes et sélecteurs d'attributs

Règle : Les pseudo-classes (: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 */

2.4 Spécificité avec :is(), :where() et :has()

Règle spéciale : Ces trois pseudo-classes ont un comportement unique de spécificité.
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 */

2.5 Pseudo-éléments

/* 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) */

3. Visualisation

3.1 Calcul de spécificité — exemples

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)

3.2 Pourquoi 99 classes ne battent pas 1 ID

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

4. Exemples progressifs

4.1 Deux sélecteurs d'élément

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

Titre rouge — h1 est en dernier dans le code CSS

4.2 Classe vs élément

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

4.3 ID vs classe

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 */
Ce bloc est rouge — l'ID gagne sur la classe

4.4 Plusieurs classes

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 */
Deux classes (.a .b) → rouge
Une seule classe (.c) → pas appliqué

4.5 ID + classe vs ID seul

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 */
ID + classe → rouge gagne sur ID seul

4.6 Pseudo-classe

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 */

4.7 :is() vs :where()

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) */

4.8 :has() hérite la spécificité

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 */

4.9 Calcul complexe

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 */

5. Erreurs fréquentes

Erreur 1 : Additionner les scores sur la même colonne
/* 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. */
Erreur 2 : Confondre :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 */
Erreur 3 : Utiliser le sélecteur universel pour augmenter la spécificité
/* 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 */
Erreur 4 : Confondre combinaison et spécificité
/* 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 */
Erreur 5 : Augmenter la spécificité inutilement
/* 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 */

6. Bonnes pratiques

Règles d'or pour la spécificité :
  • Garder la spécificité basse : préférer 1 classe (10) à des sélecteurs composites.
  • Éviter les IDs pour le CSS : ils créent des spécificités trop élevées (100) et rendent le code difficile à maintenir.
  • Utiliser :where() pour des styles facilement surchargeables (thèmes, defaults).
  • Utiliser :is() pour écrire des sélecteurs concis sans perte de spécificité.
  • Ne pas « escalader la spécificité » : si tu dois ajouter un ID pour écraser un style, c'est un signe de mauvaise architecture.
  • Mesurer la spécificité avec les DevTools du navigateur (onglet Styles → la ligne barrée indique la règle écrasée).
/* ✅ 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; }

7. Mini-défi

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 */

8. Projet d'application

Projet : Calculateur de spécificité

Crée un outil HTML/CSS/JS qui calcule la spécificité d'un sélecteur :

Fonctionnalités :

  • Un champ de saisie pour entrer un sélecteur CSS
  • L'affichage du score (a, b, c, d)
  • La décomposition : nombre d'IDs, classes, éléments
  • Un tableau de comparaison avec des sélecteurs de référence
  • Un indicateur visuel (vert = basse, orange = moyenne, rouge = haute)

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>

9. Révision

Quiz rapide

  1. Quelle est la spécificité de .ma-classe ?
  2. Quelle est la spécificité de #mon-id ?
  3. Un sélecteur #id bat-il .a .b .c .d .e (5 classes) ?
  4. Quel est le score d'un sélecteur inline style="" ?
  5. Quelle pseudo-classe a toujours spécificité 0 ?
  6. :is(#a, .b) a quelle spécificité ?
  7. Les pseudo-classes (:hover, :nth-child()) ont le même poids qu'une classe ou un élément ?
  8. Quel est le score de #header .nav a:hover ?
  9. Pourquoi ne faut-il pas multiplier les IDs dans un sélecteur ?
  10. Quelle est la différence entre initial et la valeur par défaut du navigateur ?
Réponses :
  1. (0, 0, 1, 0) = 10
  2. (0, 1, 0, 0) = 100
  3. Oui — 1 ID (100) > 5 classes (50)
  4. (1, 0, 0, 0) = 1000
  5. :where()
  6. (0, 1, 0, 0) = 100 — la spécificité du plus spécifique argument
  7. Ils ont le même poids qu'une classe : (0, 0, 1, 0)
  8. (0, 1, 1, 1) = 111 — 1 ID, 1 classe + :hover, 1 élément
  9. Parce que chaque ID ajouté augmente la spécificité de 100, rendant le style très difficile à surcharger.
  10. initial = valeur initiale dans le spéculaire CSS. La valeur par défaut du navigateur peut être différente (user-agent stylesheet).