Chapitre 716 minDébutant
Cascade, héritage et spécificité en CSS
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- comprendre pourquoi une règle CSS « ne marche pas » ;
- calculer la spécificité d'un sélecteur ;
- prévoir le résultat de la cascade et de l'héritage ;
- organiser tes styles avec
@layerplutôt qu'avec!important; - déboguer la cascade avec l'inspecteur.
4 exercices corrigés1 schéma
La cascade est l'algorithme qui décide quelle déclaration CSS s'applique quand plusieurs règles donnent des valeurs différentes à la même propriété d'un même élément ; la spécificité est le « poids » d'un sélecteur dans cette décision. Ce chapitre explique les critères de la cascade dans l'ordre, le calcul de la spécificité, !important, les couches @layer et l'héritage : tout ce qu'il faut pour comprendre pourquoi une règle « ne marche pas ».
Pourquoi une règle « ne marche pas »
Tu écris ce CSS pour la page d'accueil de la médiathèque :
#contenu p {
color: #1f2937;
}
.alerte {
color: #b91c1c;
}
<main id="contenu">
<p class="alerte">Fermeture exceptionnelle le 14 juillet.</p>
</main>
Le paragraphe reste gris foncé, alors que .alerte est écrite après. Dans l'inspecteur, color: #b91c1c apparaît barré. Ce n'est pas un bug : #contenu p est plus spécifique que .alerte, et la spécificité passe avant l'ordre d'écriture. Le reste du chapitre explique pourquoi, et comment écrire du CSS qui évite ces conflits.
Les étapes de la cascade
Pour chaque propriété de chaque élément, le navigateur rassemble toutes les déclarations qui s'y appliquent, puis les départage avec une suite de critères. Il passe au critère suivant seulement en cas d'égalité au précédent.
| Ordre | Critère | En pratique |
|---|---|---|
| 1 | Origine et importance | Les styles par défaut du navigateur (marges des titres, liens bleus soulignés) perdent contre les tiens. Une déclaration !important gagne contre toutes les déclarations normales. |
| 2 | Attribut style | style="…" dans le HTML gagne contre les feuilles de style (sauf !important). |
| 3 | Couches @layer | Entre couches, la dernière déclarée gagne ; le CSS hors couche gagne contre toutes les couches. |
| 4 | Spécificité | Le sélecteur le plus spécifique gagne. |
| 5 | Ordre d'apparition | À égalité parfaite, la dernière déclaration lue gagne (plus bas dans le fichier, ou dans un fichier chargé après). |
Dans la grande majorité des cas, tu n'as affaire qu'aux critères 4 et 5 : spécificité, puis ordre.
Calculer la spécificité d'un sélecteur
La spécificité est un triplet (A, B, C) :
- A : le nombre d'identifiants (
#contenu) ; - B : le nombre de classes (
.alerte), d'attributs ([type="email"]) et de pseudo-classes (:hover,:nth-child()) ; - C : le nombre de types (
p,nav) et de pseudo-éléments (::before).
Le sélecteur universel * et les combinateurs (espace, >, +, ~) ne comptent pour rien.
| Sélecteur | A | B | C | Triplet |
|---|---|---|---|---|
p | 0 | 0 | 1 | (0,0,1) |
nav a | 0 | 0 | 2 | (0,0,2) |
.alerte | 0 | 1 | 0 | (0,1,0) |
p.alerte | 0 | 1 | 1 | (0,1,1) |
input[type="email"]:focus | 0 | 2 | 1 | (0,2,1) |
tbody tr:nth-child(even) | 0 | 1 | 2 | (0,1,2) |
.menu li:not(:last-child) | 0 | 2 | 1 | (0,2,1) |
#contenu p | 1 | 0 | 1 | (1,0,1) |
#menu .lien:hover | 1 | 2 | 0 | (1,2,0) |
a::after | 0 | 0 | 2 | (0,0,2) |
* | 0 | 0 | 0 | (0,0,0) |
On compare deux triplets colonne par colonne, de gauche à droite, comme on compare deux dates (année, puis mois, puis jour) : la première colonne différente décide. (1,0,1) bat (0,1,0), et (1,0,0) bat même (0,12,0) : aucun nombre de classes ne bat un seul identifiant.
Trois pseudo-classes ont une règle particulière :
| Pseudo-classe | Spécificité |
|---|---|
:not(…), :is(…), :has(…) | celle de l'argument le plus spécifique de la liste ; la pseudo-classe elle-même ne compte pas. :is(#menu, .nav) a vaut (1,0,1). |
:where(…) | toujours zéro, quels que soient ses arguments. Pratique pour des styles de base faciles à surcharger. |
L'attribut style="…" passe avant toute feuille de style : on le présente parfois comme un quatrième chiffre, devant les trois autres.
Revenons à l'exemple du début : #contenu p vaut (1,0,1), .alerte vaut (0,1,0). L'identifiant décide, le paragraphe reste gris. Les solutions, de la meilleure à la pire :
- Ne pas utiliser d'identifiant dans le CSS :
.contenu p(0,1,1) contre.contenu .alerte(0,2,0), et l'alerte gagne ; - augmenter la spécificité de l'alerte :
#contenu .alerte(1,1,0) ; !important: ça marche, et c'est le début des ennuis (section suivante).
L'ordre d'apparition
À spécificité égale, la dernière règle gagne :
.bouton { background-color: #0f766e; }
.bouton { background-color: #1d4ed8; } /* gagne : même poids, écrite après */
L'ordre compte aussi entre les fichiers : une feuille chargée par un <link> placé plus bas dans le <head> passe après. C'est pour ça qu'on charge la feuille d'une bibliothèque (Bootstrap) avant la sienne : à spécificité égale, tes règles gagnent.
!important : l'exception à éviter
!important, placé après une valeur, fait passer la déclaration devant toutes les déclarations normales, quelle que soit leur spécificité :
p { color: #1f2937 !important; }
#contenu .alerte { color: #b91c1c; } /* perd, malgré son id */
Le seul moyen de battre un !important est un autre !important, plus spécifique ou écrit plus bas. D'où l'engrenage : une règle ne marche pas, on ajoute !important ; une autre ne marche plus, on en ajoute un deuxième ; au bout de quelques semaines, personne ne sait plus quelle règle s'applique.
Les usages légitimes sont rares : surcharger le CSS d'un composant tiers que tu ne peux pas modifier, ou une classe utilitaire qui doit toujours gagner (.masque { display: none !important; }). Dans ton propre code, un !important est le signe d'un sélecteur mal pensé : corrige le sélecteur.
Les couches @layer
Les couches de cascade, reconnues par tous les navigateurs depuis 2022, règlent proprement le problème des priorités entre blocs de CSS. On déclare l'ordre des couches une fois, en tête de feuille ; une couche déclarée plus tard l'emporte sur les précédentes, quelle que soit la spécificité des sélecteurs :
@layer base, composants, utilitaires;
@layer base {
#contenu a { color: #1d4ed8; } /* (1,0,1), mais dans la couche la plus faible */
}
@layer composants {
.bouton { color: white; } /* gagne contre la couche base */
}
@layer utilitaires {
.texte-centre { text-align: center; }
}
Un lien <a class="bouton"> dans #contenu est blanc : la couche composants passe avant base, malgré l'identifiant. Tu peux aussi ranger une bibliothèque entière dans une couche : @import url("bootstrap.css") layer(bibliotheque); puis déclarer @layer bibliotheque, base, composants; : tout ton CSS gagne contre elle, sans !important. Les frameworks récents, comme Tailwind CSS v4, s'appuient sur ce mécanisme.
L'héritage
Certaines propriétés se transmettent automatiquement du parent à ses descendants : c'est l'héritage. Tu déclares la police une fois sur body, et tous les paragraphes, titres et listes la reçoivent.
| Héritées (surtout le texte) | Non héritées (surtout les boîtes) |
|---|---|
color, font-family, font-size, font-weight, line-height, text-align, letter-spacing, list-style, visibility, cursor | margin, padding, border, background, width, height, display, position |
La logique : si border était héritée, chaque enfant d'un encadré aurait sa propre bordure.
Une valeur héritée a la plus faible des priorités : n'importe quelle règle qui vise l'élément lui-même, même * de spécificité (0,0,0), passe devant. C'est pour ça que tes liens restent bleus dans un paragraphe gris : le navigateur leur donne une couleur par défaut, qui bat la couleur héritée du paragraphe.
Quatre mots-clés, valables pour toutes les propriétés, pilotent l'héritage :
| Mot-clé | Effet |
|---|---|
inherit | prend la valeur du parent, même pour une propriété non héritée |
initial | revient à la valeur initiale définie par la spécification CSS (color: initial donne souvent du noir) |
unset | inherit pour une propriété héritée, initial sinon |
revert | revient à la valeur par défaut du navigateur |
/* Les champs de formulaire n'héritent pas de la police : on la leur donne */
input,
select,
textarea,
button {
font: inherit;
}
/* Un lien qui prend la couleur du texte qui l'entoure */
.lien-discret {
color: inherit;
}
Déboguer la cascade avec l'inspecteur
Sélectionne l'élément dans l'inspecteur (F12, clic droit > Inspecter). Le panneau des styles affiche :
- toutes les règles qui s'appliquent, de la plus prioritaire (en haut) à la moins prioritaire ;
- les déclarations perdantes barrées ;
- les valeurs héritées, sous « Hérité de
main#contenu» ; - le fichier et la ligne de chaque règle, cliquables.
L'onglet Calculé (Computed) donne la valeur finale de chaque propriété ; un clic sur une propriété montre quelle règle l'a fixée. Dans Chrome et Edge, survole un sélecteur : une info-bulle affiche sa spécificité.
Exercices
Exercice 1 sur 4 Calculer des spécificités Réussi À revoir
Donne le triplet de spécificité de chaque sélecteur.
header nav ul li a.carte h3#contenu .carte.completa[href$=".pdf"]::afternav > ul li a:hover:is(#accueil, .page) h2:where(#accueil, .page) h2.menu li:not(.actif):hover
Voir la réponseMasquer la réponse de l'exercice 1
- (0,0,5) : cinq types.
- (0,1,1).
- (1,2,0) : un id, deux classes.
- (0,1,2) : l'attribut compte en B ;
aet le pseudo-élément::aftercomptent en C. - (0,1,4) :
:hoveren B ;nav,ul,li,aen C. Les combinateurs ne comptent pas. - (1,0,1) :
:is()prend l'argument le plus spécifique,#accueil, plus le typeh2. - (0,0,1) :
:where()vaut toujours zéro ; il ne reste queh2. - (0,3,1) :
.menu,.actif(l'argument de:not()) et:hoveren B ;lien C.
Tu avais trouvé ?
Exercice 2 sur 4 Quelle couleur ? Réussi À revoir
Pour chaque paragraphe, donne la couleur affichée et justifie par la cascade.
<div id="page">
<p class="intro important">Paragraphe 1</p>
<p class="texte">Paragraphe 2</p>
<p>Paragraphe 3</p>
</div>
/* A */ p { color: black; }
/* B */ .intro { color: blue; }
/* C */ #page p { color: green; }
/* D */ #page .intro { color: red; }
/* E */ .intro.important { color: orange; }
/* F */ div p.texte { color: purple; }
Voir la réponseMasquer la réponse de l'exercice 2
| Règle | Sélecteur | Triplet |
|---|---|---|
| A | p | (0,0,1) |
| B | .intro | (0,1,0) |
| C | #page p | (1,0,1) |
| D | #page .intro | (1,1,0) |
| E | .intro.important | (0,2,0) |
| F | div p.texte | (0,1,2) |
- Paragraphe 1 : A, B, C, D et E s'appliquent. C et D ont chacun un id ; à égalité sur A, on compare B : D (1 classe) bat C (0). Rouge.
- Paragraphe 2 : A, C et F s'appliquent. C a un id, F n'en a pas : C gagne, malgré les deux types et la classe de F. Vert.
- Paragraphe 3 : A et C s'appliquent. Vert.
E, avec ses deux classes, ne gagne nulle part : il n'a pas d'identifiant.
Tu avais trouvé ?
Exercice 3 sur 4 Réparer sans !important Réussi À revoir
Un développeur a écrit ce CSS. Le bouton « Complet » devrait être gris, il reste vert ; il a ajouté !important, et maintenant c'est le bouton du formulaire de contact qui ne prend plus sa couleur. Explique le problème et propose un CSS propre.
#ateliers .carte a.bouton { background-color: #0f766e; }
.bouton-complet { background-color: #94a3b8 !important; }
#contact .bouton-complet { background-color: #b91c1c; }
Voir la réponseMasquer la réponse de l'exercice 3
#ateliers .carte a.bouton vaut (1,2,1) : .bouton-complet (0,1,0) ne pouvait pas gagner. Le !important a forcé le gris partout, y compris dans #contact, où la règle normale (1,1,0) perd contre lui.
La correction : supprimer les identifiants et donner le même poids aux variantes, l'ordre faisant le reste.
.bouton { background-color: #0f766e; color: white; }
.bouton-complet { background-color: #64748b; }
.bouton-danger { background-color: #b91c1c; }
Toutes les règles valent (0,1,0) : la variante, écrite après, gagne. Dans le HTML : <a class="bouton bouton-complet">. Le gris passe de #94a3b8 à #64748b, parce que le texte blanc sur #94a3b8 n'atteint que 2,6:1 de contraste, contre 4,8:1 sur #64748b.
Tu avais trouvé ?
Exercice 4 sur 4 Héritage Réussi À revoir
Avec ce CSS, donne la couleur, la police et la bordure du <strong> et du lien.
body { font-family: Georgia, serif; color: #1f2937; }
.encadre { border: 2px solid #0f766e; color: #0f766e; }
<div class="encadre">
<p>Entrée <strong>gratuite</strong>, voir le <a href="tarifs.html">détail des tarifs</a>.</p>
</div>
Voir la réponseMasquer la réponse de l'exercice 4
<strong>: couleur verte#0f766e(héritée de.encadre, plus proche quebody), police Georgia (héritée debody), pas de bordure (borderne s'hérite pas).- Le lien : police Georgia (héritée), pas de bordure, mais couleur bleue par défaut du navigateur : la feuille du navigateur donne une couleur aux liens, qui bat la couleur héritée. Pour qu'il soit vert,
a { color: inherit; }ou une règle.encadre a { color: … }.
Tu avais trouvé ?