Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

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 @layer plutô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 :

css
#contenu p {
  color: #1f2937;
}

.alerte {
  color: #b91c1c;
}
html
<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.

Toutes les déclarations d'une propriété pour un élément1. Origine et importance!important d'abord ; ton CSS avant le navigateurégalité : critère suivant2. Attribut stylestyle="…" avant les feuilles de styleégalité : critère suivant3. Couches @layerhors couche, puis la dernière couche déclaréeégalité : critère suivant4. Spécificitéle triplet (A, B, C) le plus fort gagneégalité : critère suivant5. Ordre d'apparitionla déclaration écrite en dernier gagneUne seule déclaration s'appliqueComparer deux spécificitéscolonne par colonne,de gauche à droiteABC#menu10012 classes0120#menu gagne : 1 > 0 en AB et C ne sont pas comparés
Les critères de la cascade, dans l'ordre : on ne passe au suivant qu'en cas d'égalité
OrdreCritèreEn pratique
1Origine et importanceLes 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.
2Attribut stylestyle="…" dans le HTML gagne contre les feuilles de style (sauf !important).
3Couches @layerEntre couches, la dernière déclarée gagne ; le CSS hors couche gagne contre toutes les couches.
4SpécificitéLe sélecteur le plus spécifique gagne.
5Ordre 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électeurABCTriplet
p001(0,0,1)
nav a002(0,0,2)
.alerte010(0,1,0)
p.alerte011(0,1,1)
input[type="email"]:focus021(0,2,1)
tbody tr:nth-child(even)012(0,1,2)
.menu li:not(:last-child)021(0,2,1)
#contenu p101(1,0,1)
#menu .lien:hover120(1,2,0)
a::after002(0,0,2)
*000(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-classeSpé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 :

  1. Ne pas utiliser d'identifiant dans le CSS : .contenu p (0,1,1) contre .contenu .alerte (0,2,0), et l'alerte gagne ;
  2. augmenter la spécificité de l'alerte : #contenu .alerte (1,1,0) ;
  3. !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 :

css
.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é :

css
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 :

css
@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, cursormargin, 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
inheritprend la valeur du parent, même pour une propriété non héritée
initialrevient à la valeur initiale définie par la spécification CSS (color: initial donne souvent du noir)
unsetinherit pour une propriété héritée, initial sinon
revertrevient à la valeur par défaut du navigateur
css
/* 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.

  1. header nav ul li a
  2. .carte h3
  3. #contenu .carte.complet
  4. a[href$=".pdf"]::after
  5. nav > ul li a:hover
  6. :is(#accueil, .page) h2
  7. :where(#accueil, .page) h2
  8. .menu li:not(.actif):hover
Voir la réponseMasquer la réponse de l'exercice 1
  1. (0,0,5) : cinq types.
  2. (0,1,1).
  3. (1,2,0) : un id, deux classes.
  4. (0,1,2) : l'attribut compte en B ; a et le pseudo-élément ::after comptent en C.
  5. (0,1,4) : :hover en B ; nav, ul, li, a en C. Les combinateurs ne comptent pas.
  6. (1,0,1) : :is() prend l'argument le plus spécifique, #accueil, plus le type h2.
  7. (0,0,1) : :where() vaut toujours zéro ; il ne reste que h2.
  8. (0,3,1) : .menu, .actif (l'argument de :not()) et :hover en B ; li en 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.

html
<div id="page">
  <p class="intro important">Paragraphe 1</p>
  <p class="texte">Paragraphe 2</p>
  <p>Paragraphe 3</p>
</div>
css
/* 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ègleSélecteurTriplet
Ap(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)
Fdiv 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.

css
#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.

css
.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.

css
body { font-family: Georgia, serif; color: #1f2937; }
.encadre { border: 2px solid #0f766e; color: #0f766e; }
html
<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 que body), police Georgia (héritée de body), pas de bordure (border ne 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é ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Box model, display et position en CSSRevoir le sommaire du cours