En CSS, il y a les sélecteurs. Avec le CSS v3, il y a maintenant beaucoup de sélecteurs, et vous trouverez pas mal de ressources à ce sujet. En revanche, ce dont on n’entend pratiquement jamais parler, c’est la spécificité des sélecteurs. Même les sites de référence comme OpenClassRooms ne semble pas en parler dans son tutoriel sur le HTML/CSS.

La spécificité est pourtant importante, car elle intervient au moment où le navigateur doit appliquer les CSS à un élément de la page.


Spécificité ?


Considérons ce code :

HTML :
<body>
Mon texte
</body>

CSS:
body { color: red; }
body { color: green; }

Quelle sera la couleur de la page ?
La page sera évidemment verte, car le navigateur va prendre en compte la dernière déclaration effectuée.

Maintenant, plaçons des paragraphes dans la page avec un lien :

<body>
Mon texte
<p>Mon paragraphe</p>
<p>Mon paragraphe avec <a>un lien</a></p>
</body>

Avec le code ci-dessus, les paragraphes et le lien seront également en vert (en fait non pour les liens : les styles internes du navigateur font qu’ils sont bleus si on ne dit rien ; voir plus bas) : la couleur est en effet héritée de l’élément parent de <p> et de <a>.
Si on veut donner une couleur particulière aux éléments on fait ceci :

CSS:
body { color: red; }
p { color: green; }
a { color: blue; }


Ici, le texte sera bleu dans le lien, en vert dans le paragraphe et en rouge partout ailleurs dans le corps du document.
La couleur est bien héritée, mais elle est écrasée si on la déclare de nouveau dans un élement qui est plus proche (p est plus proche de a que le body).

Simple non ? Bien.


Maintenant, prenons le code CSS suivant :

CSS:
body p { color: red; }
p { color: green; }

Quelle sera la couleur du paragraphe ? Indice : ça n’est pas vert !
Ici, la première déclaration veut dire « tous les paragraphes dans le corps du document doivent être en rouge » et la seconde déclaration « tous les paragraphes doivent être en vert ».

Alors pourquoi le paragraphe est rouge et pas vert ?

Le paragraphe est rouge parce que le sélecteur « body p » est plus spécifique que le sélecteur « p » tout seul. Il est en effet plus précis de dire « tous les paragraphes dans le corps du document » que « tous les paragraphes ».
Or, pour le navigateur qui va afficher la page, la spécificité est prioritaire sur l’ordre des déclarations dans le code.


Sachant ceci, vous pouvez prédire quelle sera la couleur du paragraphe quand on lui applique ce code :

CSS:
body p { color: red; }
body p { color: blue; }
p { color: green; }
p { color: orange; }
p { color: fuchsia; }

Le paragraphe sera en bleu.
Les deux premiers sélecteurs sont plus spécifiques que les trois suivantes : elles sont donc prioritaires. La seconde est située après la première : c’est donc elle qui est retenue.


Spécificité de chaque sélecteur


Si on reprend ce CSS :
body p { color: red; }
p { color: green; }

Je vous ait dit que la première déclaration est plus spécifique. En fait, il y a deux sélecteurs d’éléments (body et p). La spécificité d’éléments est donc de 2. La seconde déclaration possède un seul sélecteur d’élément : sa spécificité d’éléments est 1 : la première est bien prioritaire.

Maintenant, donnons des classes à nos éléments :

<body>
Mon texte
<p class="in-blue">Mon paragraphe</p>
</body>

Et ajoutons le CSS suivant :
body p { color: red; }
p { color: green; }
p.in-blue { color: blue; }

Quelle sera la couleur du paragraphe ? Bleue, encore !

En CSS, le sélecteur de classe l’emporte sur les sélecteur d’éléments, même s’il y en a plusieurs ! Vous pouvez bien faire « html body div p span a { … } », si votre lien <a> possède une classe « foo », alors le code « .foo { … } » sera prioritaire.

C’est une question de hiérarchie : les classes sont plus fortes que les éléments, même si les éléments sont au nombre 315 et que la classe est toute seule : un sélecteur plus haut dans la hiérarchie sera toujours prioritaire pour imposer ses styles.

Comme les éléments et les classes, chaque type de sélecteur (ID, attributs…) possède sa place dans la hiérarchie.


Calcul de la spécificité


On a vu que la classe est prioritaire sur les éléments. Mais les ID sont prioritaires sur les classes. Et le CSS « inline » (attaché dans l’attribut style="…" d’un élément) est prioritaire sur les ID. Enfin, le flag « !important » est prioritaire sur tous les autres, quelque que soit sa place.

On a donc quelque chose comme ça :

Y-X-C-B-A


Avec :

  • Y : la présence du « !important »
  • X : le CSS est Inline
  • C : le sélecteur d’ID
  • B : le sélecteur de classe (les pseudo-classe telles que « :nth-of-type() », ou « :hover » et d’attributs comme « [href=""] » vont également ici)
  • A : le sélecteur d’élément (les sélecteurs de pseudo-éléments telles que « ::before » ou « ::first-line » vont ici)

Chaque lettre A, B, C, X, Y représente un nombre, où 0 est le défaut.

Voici quelques exemples :

* { color: green; }
Ceci donne un style vert à tous les éléments. Sa spécificité est cependant nulle : 0-0-0-0-0. Elle est supplantée par tous les autres sélecteurs, mais elle permet quand même d’être plus forte que les styles « par défaut » du navigateur (j’y reviendrai plus tard).

p { color: green; }
Pas de !important, ni de style en ligne, ni d’ID, ni de classe, mais un sélecteur d’élément : la spécificité est 0-0-0-0-1.

body p { color: red; }
Pas de !important, ni de style en ligne, ni d’ID, ni de classe, mais deux sélecteurs d’élément : la spécificité est 0-0-0-0-2.

p.in-blue { color: blue; }
Ici, on a une classe et un élément : la spécificité est 0-0-0-1-1.

body p.in-gray { color: gray; }
On a un élément (body), un autre élément (p) et une classe : la spécificité est 0-0-0-1-2.

body.home p.in-black { color: black; }
Ici, au total on deux éléments et deux classes : la spécificité est 0-0-0-2-2.

p.in-black.in-bold { color: black; font-weight: bold; }
Ici, au total on un élément et deux classes (sur le même élément, certes) : la spécificité est 0-0-0-2-1.

body#home p.in-black .link span { color: black; }
On a 3 éléments, 1 ID et deux classes : 0-0-1-3-2.

body#home table#data tbody tr td a .cool { color: black; }
6 éléments, deux ID et une classe : 0-0-2-1-6.

C’est assez simple non ?

Pour le « !important », il faut faire attention : ce dernier ne s’applique qu’à une seule propriété :

#home p {
    color: black;
    font-weight: bold;
}
p {
    color: red!important;
    font-weight: normal;
}

Ici, la couleur est mise en !important dans la seconde déclaration : la spécificité de la couleur est donc :
  • 0-0-1-0-1 dans la première déclaration
  • 1-0-0-0-1 dans la seconde

Mais la spécificité pour la graisse (texte en gras) est de :
  • 0-0-1-0-1 dans la première déclaration
  • 0-0-0-0-1 dans la seconde

Le paragraphe a donc une couleur rouge (grâce au !important) mais un texte en gras, à cause de spécificité du sélecteur dans la première déclaration.

Vous arrivez à suivre ?
Continuons.

p.foo a[href] {…}
La spécificité du sélecteur d’attribut est la même que celle des classes. On a donc l’équivalent de deux classes et deux éléments : 0-0-0-2-2.

p.foo > a{…}
p.foo a{…}
Ici, les liens a ont la même spécificité : les combinateurs comme >, ~, + n’ont pas de spécificité particulière et ne sont pas prioritaires.

Ce qui suit est plus tordu :
HTML:
<body id="home><p><a>coucou!</a></p></body>

#home p { color: red!important }  /* 1-0-1-0-1 */
a{ color: green }  /* 0-0-0-0-1 */

Le lien sera… Vert !
En effet, bien que la spécificité de la première déclaration l’emporte sur la seconde, la couleur de la première s’applique au paragraphe et non au lien !
L’héritage existe, mais la seconde déclaration s’applique directement aux liens : elle est donc plus forte que l’héritage. L’ordre n’aurait pas non plus importé et le lien aurait été vert même dans le cas suivant :

a{ color: green }
#home p { color: red }

Si on avait voulu forcer la couleur rouge des liens, il aurait fallu faire ça :
#home p a { color: red }  /* 0-0-1-0-2 */
a{ color: green }  /* 0-0-0-0-1 */

Ici alors les deux sélecteurs s’adressent aux liens directement, et c’est la spécificité qu’il faut regarder.

La même remarque compte pour les pseudo-éléments :
#home a::before{ color: green }  /* 0-0-1-0-2 */
#home p a { color: red }  /* 0-0-1-0-2 */

La spécificité de la couleur est de 0-0-1-0-2 dans les deux cas, mais la première s’applique directement au ::before du lien, qui est un pseudo-élement : il est donc prioritaire sur l’héritage ainsi que sur l’ordre de la déclaration.

En revanche, les pseudo-classes restent des classes (ce ne sont pas des éléments particuliers) et ont la même spécificités que les classes.
Du coup, le code suivant :
#top a { color: red; } /* 0-0-1-0-1 */
a:hover {color: green} /* 0-0-0-1-1 */

Donnera un lien rouge même quand on passera la souris dessus ! Les deux sélecteurs s’appliquent directement au lien (pas de problème d’héritage) mais le « #top » de la première déclaration est plus spécifique que le « :hover », qui ne compte que pour une classe.

Pour faire passer l’effet lors du passage de la souris, il faut utiliser une des méthodes suivantes :
#top a:hover {color: green} /* 0-0-1-1-1 */
a:hover {color: green!important} /* 1-0-0-1-1 */
Ou bien utiliser JavaScript, dans ce genre là :
<a onmouseover="this.style.color='green'">
(Les styles ajoutées en JS avec onmouseover/onblur équivalent à ajouter des styles directement sur l’élément, et la spécificité sera de 0-1-0-0-0, bien au dessus d’un sélecteur d’ID ou de classe.

Enfin, un petit mot sur la pseudo-classe « :not() » : elle n’ajoute pas de spécificité particulière, mais il faut quand même compter tout ce qui se trouve à l’intérieur :

#top a:not(.links) {color: green} /* 0-0-1-1-1 */
On se retrouve avec un ID, un élément ainsi qu’une classe : elle se trouve dans le :not(), il faut la compter comme une classe. Si le :not() contenait un ID, il faudrait le compter comme un ID.


Styles navigateur, auteur, utilisateur


On a vu tout ce qui concerne la spécificité des sélecteurs, mais il reste un dernier point à aborder.
Pour le moment, on a parlé du CSS dans une page web. Ce ne sont pourtant pas les seules styles qui sont utilisées sur une page.

Par exemple, quelle est le style d’un lien sur une page qui n’a aucun CSS ? Le lien est bleu dans pratiquement tous les navigateurs (il est même violet quand le lien est visité). Pareil, certains éléments sont stylisés de façon particulières : les formulaires ont des bordures et tous les éléments sont blancs.
Les styles ici sont produites par le navigateur lui-même : ce sont les styles navigateur.

Parallèlement, un utilisateur peut forcer l’application de styles : il veut par exemple mettre tous les liens en orange, et toute la page en Comic Sans MS. Les navigateurs disposent d’une feuille de style utilisateur prévu pour que l’utilisateur puisse ajouter ce qu’il veut. Ceci inclut les styles introduites par les modules complémentaires, comme les bloqueurs de publicité.

On se retrouve donc avec 3 sources de CSS :

  • le style navigateur : appliqué par défaut si la page ne prévoit aucun code CSS ;
  • le style utilisateur : inclus par l’utilisateur.
  • le style auteur : inclus dans la page par l’auteur de la page ou du site ;

En général, les navigateurs appliquent les styles dans l’ordre ci-dessus. C’est donc l’auteur de la page qui a le dernier mot.

Il y a une exception cependant : le « !important » dans les styles utilisateur. Ce sélecteur, le plus puissant de tous, devient encore plus puissant quand il est utilisé dans les styles utilisateurs.
Les navigateurs laissent donc le dernier mot à l’utilisateur et c’est bien normal.

Pour Firefox, le fichier des styles utilisateurs se trouve dans le dossier chrome du profil de Firefox : ~/.mozilla/firefox/*.default/chrome/userContent.css (sous GNU/Linux).
Vous pouvez essayer dans ce fichier : ajoutez simplement ceci :
a { color: green!important; }

Enregistrez, puis redémarrez Firefox et vous verrez que tous les liens seront en vert, même si l’auteur a utilisé un sélecteur beaucoup plus puissant, avec à la fois !important et un ID, par exemple.

(Pour remettre les liens normalement, supprimez le ligne du fichier userContent.css, enregistrez, puis relancez Firefox.)

Les autres navigateurs doivent également proposer des styles utilisateurs quelque part.
Ceci est essentiel pour les personnes malvoyantes ou dyslexiques, qui ont besoin de polices de caractères spéciales, en grand et avec un contraste élevé.


Ressources


Comme j’ai dit en intro, il y a peu de documentation à propos de la spécificité en CSS, mais on trouve quand même ceci :


Pour conclure, je pense avoir fait le tour de la question, et j’espère que ça vous permettra de déboguer votre CSS un peu plus facilement.
Je dirais une nouvelle fois que ça me surprends de ne pas trouver plus de documentation à propos de la spécificité, alors que si on regarde d’autres choses, comme les nouveaux sélecteurs CSS3 ou les couleurs HTML, tout le monde en parle et les connaît.

14 commentaires

gravatar
maxim a dit :

Un grand merci pour ce rappel

gravatar
Yuzmi a dit :

Merci pour les précisions. Je comprends mieux le fonctionnement.

gravatar
workflo a dit :

Bonjour,

Quand j'étais en licence professionnelle (septembre 2005 à avril 2006 (hors stage)), on nous en a parlé et on a fait des exercices dessus ! Cela faisait partie du programme.
:-)

gravatar
John Doe a dit :

Bonjour,

merci beaucoup pour cet article très bien expliqué.

Une question cependant.

J'utilise un thème sombre sous ubuntu 14.04. Firefox semble l'utiliser pour contrôler l'apparence de certains éléments sur les pages web, les rendant parfois difficiles à lire.

Par exemple le champ de saisie sur YouTube où la police d'écriture était gris très clair sur fond blanc. J'ai trouvé la solution que tu indiques dans ton article, à savoir créer un fichier userContent.css dans lequel appliquer le style désiré, en l'occurrence dans mon cas :

select, input, textarea {color:#555;background-color:#fff; !important;}

Ça a effectivement réglé le problème sur YouTube et d'autres sites mais pas partout.
Jusqu'à aujourd'hui, certains champs de saisie, boutons ou menus déroulants était presque illisibles. Par exemple sur ambient-mixer, j'avais ça.

J'ai fini par trouver une solution ici.

Mon userContent.css ressemble maintenant à ça :

select, input, textarea {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}

select, input, textarea {color:#555;background-color:#fff; !important;}


Pourquoi firefox n'appliquait pas le style que j'avais défini dans mon userContent.css, malgré la présence du !important ?
Et est-ce que tu penses que c'est une solution pérenne ou qui risque de casser rapidement ?

gravatar
Le Hollandais Volant a dit :

@John Doe : ajoutes aussi "background-image: none;".

Si c'est une image qu'il y a en fond, changer juste la couleur ne changera rien. Sinon, moz-appearance est une propriété en plus de la couleur. Il faut la changer aussi.

À une époque j'avais également ce problème avec les thèmes sombres.

gravatar
John Doe a dit :

@Le Hollandais Volant :
Je comprends un peu mieux.^^
Du coup ça fait 4 propriétés css (-moz-appearance, color, background-color, background-image) à modifier pour être sûr d'avoir la couleur qu'on veut.

Encore merci, super article !

gravatar
Le Hollandais Volant a dit :

@John Doe : il faut changer tout ceux que le thème sombre fournit à Firefox. Ça peut-être, a priori, toutes les propriétés qu’on veut.

Il peut aussi être possible d’éditer les thèmes d’Ubuntu pour empêcher le thème de modifier les pages web. Mais je ne sais pas comment faire là.

gravatar
John Doe a dit :

N'empêche, pour quelqu'un qui connaît pas le css, c'est difficile d'aller trouver la propriété css qui pose problème.

Par exemple quand tu veux une police blanche sur fond sombre dans les champs de saisie, il suffit d'aller modifier color et background-color. Ça va c'est simple.
Mais si tu veux l'inverse, police sombre sur fond clair. Là ça devient compliqué à cause de -moz-appearance qui, apparemment, importe une couleur du thème système pour le fond de certains éléments html et semble avoir la priorité sur ton background-color.

J'ai cherché une méthodologie à suivre pour résoudre le problème si ça venait à se représenter, et le mieux que j'ai trouvé c'est ça :

1. Ouvrir l'inspecteur DOM (<C-S-c>).
2. Sélectionner l'élément html dont la couleur dérange.
3. Lire l'onglet Rules à la recherche d'une règle qui modifie sa couleur ou son apparence.
Éventuellement s'aider d'un sélecteur de couleur pour trouver son code hexa, et ouvrir les fichiers main.css / forms.css à la recherche d'une occurrence de color.
4. Si rien trouvé, passer à l'onglet Computed (ce sont les valeurs des propriétés css attribuées par l'auteur du site j'imagine...).
5. Si rien trouvé, cocher la case Browser styles (je suppose que cette fois ça affiche les valeurs attribuées par le navigateur...) et recommencer.
C'est d'ailleurs là qu'on trouve -moz-appearance... bonne chance pour aller le trouver quand on n'y connaît rien. xD

Normalement, le souci ne devrait même pas exister sous firefox, puisqu'on peut faire :
Edit > Preferences > Content > Colors > décocher Use system colors.

Mais chez moi ça marche pas, du coup je m'en tiens au fichier userContent.css qui lui fonctionne bien.
Et puis c'est plus marrant, ça permet d'apprendre des trucs.^^

Genre, j'ai découvert dans l'inspecteur DOM qu'il y avait un mode 3D.
Je sais pas si ça sert à quelque chose, mais ça en jette, et puis ça permet de bien se rendre compte qu'une page web c'est la somme d'un très grand nombre d'éléments différents.

gravatar
John Doe - un autre :) a dit :

Bonjour,

Tout d'abord merci pour cet article détaillé :)

Il me semble qu'une petite coquille s'est glissée dans le texte:


body#home p.in-black .link span { color: black; }
On a 3 éléments, 1 ID et deux classes : 0-0-1-3-2.

La spécificité ne devrait-elle pas être 0-0-1-2-3 dans ce cas?

gravatar
jairiencompris a dit :

Pourriez-vous préciser comment on compare les spécificités calculées ? En gros qui gagne ? IL faut ajouter à chaque chiffre et on compare les sommes ?

gravatar
Le Hollandais Volant a dit :

@jairiencompris : non, il faut les voir comme des rangs.
Un comme les rangs que sont "centaines", "dizaines", "unités" : 1 centaine l’emporte sur 2 dizaines.

Ici, 1 ID l’emporte sur 2 classes, tout simplement parce que l’ID est prioritaire sur les classes.

Prenons cet exemple, pris dans l’article :

#home p a { color: red } /* 0-0-1-0-2 */
a{ color: green } /* 0-0-0-0-1 */


C’est le premier qui gagne, car son rang le plus fort est le 3e alors que celui de la ligne 2, c’est le 5e.

En gros, il faut comparer rang par rang, de la gauche vers la droite.

Dans le même exemple on fait donc :
— rang 1 : on a 0 et 0. Résultat, on regarde le rang suivant.
— rang 2 : on a 0 et 0. On regarde le rang suivant.
— rang 3 : on a 1 et 0. Ici, le premier l’emporte : c’est lui qui gagne, et il est inutile d’aller plus loin dans la comparaison.

Si on avait des rangs 0-0-1-12-4 et 0-0-2-0-0, ce serait le second qui gagne : le rang le plus haut placé gagne : 2 l’emporte sur le 1.

Les commentaires sont fermés pour cet article