Je me souviens d'un jeu auquel mes amis et moi jouions quand nous étions enfants. C'était un jeu assez simple dans la pratique, mais qui nous amusait énormément.
Nous avions plusieurs tubes remplis d'une sorte d'eau colorée. Il y avait différentes couleurs, et chacune avait un niveau de priorité. Pour simplifier, imaginons que nous avions quatre couleurs :
Vert — priorité 3
Violet — priorité 2
Rouge — priorité 1
Jaune — priorité 0
Le principe était simple : nous prenions de l'eau normale, puis nous ajoutions l'une de ces eaux colorées. Si nous ajoutions du jaune, l'eau devenait jaune. Mais si nous ajoutions ensuite du violet, l'eau devenait violette, parce que le violet avait une priorité supérieure au jaune.
Et si nous ajoutions ensuite du rouge, rien ne changeait. Pourquoi ? Parce que le rouge avait une priorité inférieure au violet.
En revanche, si nous ajoutions du vert, alors là, l'eau changeait immédiatement de couleur et devenait verte, puisque le vert possédait la priorité la plus élevée. Et peu importe ensuite combien de fois nous ajoutions du jaune, du rouge ou du violet : tant que nous n'ajoutions pas une couleur ayant une priorité supérieure au vert, la couleur du mélange ne changeait pas.
À l'époque, nous jouions simplement avec ça parce que nous trouvions amusant de voir l'eau changer de couleur. Mais des années plus tard, cette petite histoire m'est revenue en tête pour une raison complètement différente.
Le jour où mon CSS refusait de m'écouter
Je me souviens d'un moment où je suivais un tutoriel CSS sur YouTube. Le développeur expliquait quelque chose de relativement simple : afficher un paragraphe sur une page et lui appliquer quelques styles. Je suivais exactement ce qu'il faisait et tout fonctionnait très bien.
Mais comme j'aimais déjà expérimenter par moi-même, j'ai décidé de m'amuser un peu avec le code. J'ai donc créé mon fichier HTML, auquel j'ai relié mon fichier CSS. Puis, sur mon élément <p>, j'ai commencé à mettre plusieurs styles. Je lui ai donné une classe :
<p class="paragraphe" id="pa">Bonjour tout le monde</p>
Puis dans mon fichier CSS, j'ai écrit :
.paragraphe {
color: green;
}
#pa {
color: yellow;
}
Et comme si cela ne suffisait pas, j'ai également décidé de mettre un style directement dans la balise :
<p class="paragraphe" id="pa" style="color: blue;">
Bonjour tout le monde
</p>
J'étais débutant, j'avais appris plusieurs façons d'appliquer du CSS et je voulais évidemment toutes les essayer en même temps.
J'ouvre donc mon navigateur. Et là…
Le texte est bleu.
Rendu - le navigateur applique le style en ligne, malgré la classe et l'ID
Bonjour tout le monde
Le texte s'affiche en bleu : le style inline gagne, peu importe que la classe dise vert et l'ID jaune.
Je regarde mon code. Mais où sont passés le vert et le jaune ? J'avais pourtant écrit :
.paragraphe {
color: green;
}
Et :
#pa {
color: yellow;
}
Alors pourquoi mon paragraphe était-il bleu ?
J'ai commencé à relire mon code. Encore une fois. Puis encore. Tout semblait pourtant correct. La classe était bien présente. L'ID était bien présent. Le CSS était bien relié au HTML.
Alors pourquoi le navigateur semblait-il ignorer mes deux autres couleurs ? C'est en cherchant à comprendre que je suis tombé sur une notion que je ne connaissais pas encore : la spécificité CSS.
Et à ce moment-là, mon histoire de tubes d'eau colorée m'est revenue en tête. Parce qu'en réalité, sans le savoir, j'étais en train de reproduire exactement le même principe.
Plusieurs règles CSS peuvent viser le même élément
Lorsqu'un élément HTML reçoit plusieurs règles CSS qui définissent la même propriété, le navigateur doit déterminer quelle règle doit finalement être appliquée. Prenons mon exemple :
.paragraphe {
color: green;
}
#pa {
color: yellow;
}
Et :
<p class="paragraphe" id="pa">Bonjour</p>
Les deux règles ciblent bien le même paragraphe. La première dit : « Mets le texte en vert. » La deuxième dit : « Non, mets-le en jaune. »
Alors laquelle doit être écoutée ? C'est ici que la spécificité entre en jeu. La spécificité est, en quelque sorte, un système qui permet au navigateur de déterminer quelle règle est la plus précise lorsqu'il y a plusieurs règles concurrentes. Et c'est là que mon histoire d'eau colorée devient intéressante.
Le poids des sélecteurs
En CSS, les différents types de sélecteurs n'ont pas le même poids. De manière simplifiée, on peut retenir cette hiérarchie :
ID > classe / attribut / pseudo-classe > élément / pseudo-élément
On peut représenter cela approximativement ainsi :
ID → 100
Classe → 10
Élément → 1
Ce ne sont pas des points que le navigateur additionne exactement comme une simple note sur 100, mais cette représentation est très pratique pour comprendre le principe.
Par exemple :
p {
color: blue;
}
Ici, nous ciblons un élément. On peut donc retenir : p → 1
Avec :
.paragraphe {
color: green;
}
nous ciblons une classe : .paragraphe → 10
Et avec :
#pa {
color: yellow;
}
nous ciblons un ID : #pa → 100
Donc si les trois règles ciblent le même paragraphe :
p {
color: blue;
}
.paragraphe {
color: green;
}
#pa {
color: yellow;
}
le navigateur comprend que : 1 < 10 < 100. L'ID est donc plus spécifique que la classe, et la classe est plus spécifique que l'élément.
Le texte sera donc jaune.
La suite (pourquoi le dernier écrit peut quand même gagner, comment combiner plusieurs sélecteurs, et le cas de !important) est réservée aux contributeurs.
Et dans mon cas, pourquoi était-il bleu ?
Parce que j'avais ajouté une quatrième règle. Le fameux :
style="color: blue;"
Ce style est directement écrit dans l'attribut style de l'élément HTML. Il possède donc une spécificité encore plus forte que les sélecteurs classiques comme les classes et les ID.
C'est pour cela que mon navigateur affichait finalement : BLEU.
Ma classe disait : « Vert ! » Mon ID disait : « Jaune ! » Mais mon style directement placé sur l'élément disait : « Bleu. »
Et le navigateur a retenu le bleu.
À ce moment-là, j'ai compris que mon navigateur ne faisait pas simplement : « Je prends le dernier style écrit. » Il devait d'abord déterminer quelle règle avait le plus de poids. Et cela change énormément la manière de comprendre les problèmes de CSS.
Mais attention : le dernier écrit peut quand même gagner
C'est ici qu'il faut faire attention à une autre chose. Dire que « le dernier style gagne » est une simplification que l'on entend souvent lorsqu'on débute.
En réalité, l'ordre d'apparition intervient notamment lorsque les règles sont équivalentes en spécificité. Par exemple :
.paragraphe {
color: green;
}
.paragraphe {
color: red;
}
Les deux sélecteurs ont exactement la même spécificité. Dans ce cas, la deuxième règle l'emporte. Le navigateur arrive donc à quelque chose comme : « Les deux règles ont le même poids. Alors je prends celle qui arrive en dernier. »
Mais si nous avons :
#pa {
color: yellow;
}
.paragraphe {
color: red;
}
la classe arrive après l'ID. Pourtant, le texte reste jaune. Pourquoi ? Parce que : ID > classe. L'ordre ne suffit donc pas à lui seul.
On peut même combiner plusieurs sélecteurs
Jusqu'ici, nous avons vu des situations assez simples :
p {
color: blue;
}
.paragraphe {
color: green;
}
#pa {
color: yellow;
}
Les trois règles ciblent le même paragraphe. Mais en CSS, nous pouvons aussi combiner plusieurs sélecteurs pour être encore plus précis. Prenons cet exemple :
<div id="sidebar" class="card">
<h2 class="title">Bonjour</h2>
</div>
Regardons maintenant ces deux règles :
.card .title {
color: green;
}
#sidebar .title {
color: red;
}
Cette fois, les deux règles ciblent bien le même <h2>. Pourquoi ? Parce que notre <h2> possède la classe title, et qu'il se trouve à l'intérieur d'un élément qui possède à la fois la classe card et l'ID sidebar.
La première règle dit : « Je veux le .title qui se trouve dans .card. » La deuxième dit : « Je veux le .title qui se trouve dans #sidebar. » Et notre <h2> correspond aux deux.
Nous avons donc encore une fois deux règles qui veulent définir la même propriété. Alors laquelle va gagner ? C'est ici que la spécificité nous aide.
Pour .card .title, nous avons deux classes : .card → 1 classe, .title → 1 classe. On peut donc retenir : 0 ID + 2 classes → 0-2-0
Pour #sidebar .title, nous avons : #sidebar → 1 ID, .title → 1 classe. Donc : 1 ID + 1 classe → 1-1-0
Et même si le premier sélecteur possède deux classes contre une seule dans le deuxième, le deuxième possède quelque chose de plus spécifique : un ID. Donc : #sidebar .title gagne. Le titre sera finalement rouge.
Cela permet de comprendre une chose importante : on ne compte pas simplement le nombre de sélecteurs. On regarde leur type et leur niveau de spécificité. Un ID a plus de poids qu'une classe. Une classe a plus de poids qu'un sélecteur d'élément.
C'est un peu comme notre jeu d'eau colorée : deux couleurs peuvent être présentes, mais elles n'ont pas forcément le même niveau de priorité.
Et surtout, il faut retenir que la spécificité ne sert à départager que des règles qui peuvent réellement s'appliquer au même élément. Si j'avais écrit :
<div class="card">
<h2 class="title">Titre de la carte</h2>
</div>
<div id="sidebar">
<h2 class="title">Titre de la sidebar</h2>
</div>
alors :
.card .title {
color: green;
}
#sidebar .title {
color: red;
}
ne seraient pas en conflit. La première règle viserait le titre de la carte. La deuxième viserait le titre de la sidebar. Il n'y aurait donc aucune bataille de spécificité entre ces deux règles.
Pour qu'il y ait un conflit, plusieurs règles doivent pouvoir cibler le même élément et définir la même propriété.
Et puis il y a le fameux !important
Il existe encore quelque chose que l'on rencontre très souvent lorsqu'on commence à avoir des problèmes de CSS :
p {
color: blue !important;
}
!important modifie fortement la manière dont la cascade CSS est résolue. Il peut permettre à une déclaration de prendre le dessus sur des déclarations ordinaires qui auraient autrement gagné selon la spécificité.
C'est justement pour cela qu'il faut éviter d'utiliser !important comme solution automatique à chaque problème. Parce qu'on peut rapidement entrer dans une situation où une règle écrase une autre, puis où l'on ajoute un autre !important pour la combattre, puis encore un autre… Et soudain, notre CSS devient beaucoup plus difficile à comprendre et à maintenir.
Finalement, mon histoire d'eau avait beaucoup plus de sens que je ne le pensais
Quand j'étais enfant, je jouais simplement avec des tubes d'eau colorée. Je ne pensais évidemment pas qu'un jour cette image me permettrait de comprendre une notion de CSS. Mais le principe était finalement assez proche.
Dans mon jeu, plusieurs couleurs pouvaient être ajoutées au même récipient, mais la couleur ayant la plus grande priorité finissait par déterminer la couleur visible. En CSS, plusieurs règles peuvent également vouloir définir la même propriété sur le même élément.
Et lorsque cela arrive, le navigateur doit déterminer quelle règle doit finalement être appliquée. Il regarde notamment la cascade, la spécificité et l'ordre des règles pour parvenir à une décision.
C'est donc un peu comme si plusieurs personnes donnaient des instructions au même élément, mais que toutes n'avaient pas la même autorité.
Et c'est probablement l'une des premières choses que j'aurais aimé comprendre lorsque j'ai commencé à apprendre CSS. Parce que lorsque ton style ne s'applique pas, le problème n'est pas forcément que tu as mal écrit ton CSS.
Parfois, ton CSS est parfaitement correct. C'est simplement qu'une autre règle parle plus fort que la tienne.
Et aujourd'hui, lorsqu'un style refuse de s'appliquer, au lieu de regarder immédiatement la dernière ligne que j'ai écrite, je commence par me poser une autre question :
Quelle autre règle est en train de gagner contre la mienne ?