Sur :not et la spécifité

Ire Aderinokun va toujours fouiller au fond des choses. Elle revisite ici la pseudo-classe de négation, nous montre l'envers du décor et nous offre un crash course sur la spécificité!

Par

La pseudo-classe de négation :not peut être extrêmement utile. Elle nous permet de cibler des éléments en fonction d’attributs qu’ils n’ont pas. Ce faisant, elle nous évite le recours à des règles supplémentaires et toujours plus spécifiques.

Un exemple assez courant serait celui d’une liste à laquelle nous voulons appliquer un style — sauf au dernier item :

/* Sans :not */
li { border-right: 1px solid #000; }  
li:last-child { border-right: none; }

/* Avec :not */
li:not(:last-child) { border-right: 1px solid #000; }  

La pseudo classe :not fait partie de mes préférées. Cependant, je me suis parfois trouvée dans des situations où la déclaration :not n’écrasait pas la déclaration initiale. Par exemple :

a:not(.ul) { text-decoration: none; }  
nav a { text-decoration: underline; }  

Dans ce cas précis, je me suis aperçue que les éléments nav a n'étaient toujours pas soulignés. Après un moment de confusion, j’ai approfondi ma recherche sur le fonctionnement de la règle :not et son effet sur la spécificité.

La spécificité en 5 sec

La spécificité en CSS peut être compliquée à comprendre, le mieux est de commencer par un exemple. Prenons l’élément HTML suivant :

<p class="foo" id="bar">Lorem ipsum dolor sit amet.</p>  

et les styles que voici :

p { color: red; }  
.foo { color: green; }
#bar { color: blue; }
p.foo#bar { color: yellow; }  

Dans les cas comme celui-ci, où plusieurs sélecteurs ciblent le même élément, c’est la spécificité du sélecteur qui déterminera les règles qui prévaudront.

La spécificité d’un sélecteur dépend de deux facteurs :

  1. De quels types de sélecteur s’agit-il ? Il y a trois types de sélecteurs :
    a. les ID, par exemple #bar
    b. les classes (dont les pseudo-classes), par exemple .foo ou :last-child
    c. les types, par exemple p
  2. Le nombre de chacun des sélecteurs. Par exemple s’il y a 2 IDs et 3 classes.

Ces deux facteurs combinés déterminent le niveau de spécificité d’un sélecteur. Si nous revenons à notre exemple, voici comment est calculée la spécificité de chaque sélecteur.

SélecteurNb d’IDsNb de ClassesNb de TypesSpécificitéVainqueur
p0010-0-1 
.foo0100-1-0 
#bar1001-0-0 
p.foo#bar1111-1-1

Pour lire le résultat figurant dans la colonne “Spécificité”, on ne prend pas le nombre global, mais chaque unité qui le compose, de gauche à droite.

Quand on compare deux sélécteurs, on prend la première valeur (qui représente le nombre de sélecteurs ID) et on les compare. Si un sélecteur a une valeur ID plus élevée, il gagne automatiquement la bataille. Si, et seulement si, les deux sélecteurs ont la même valeur ID, on passe à la suivante (dans ce cas, c’est le nombre de classes) et ainsi de suite.

Prenons l’exemple suivant :

SélecteurNb d’IDsNb de ClassesNb de TypesSpécificitéVainqueur
p0010-0-1 
p:last-child0110-1-1 
p.foo.bar.baz0310-3-1 
#bar1001-0-0

Un seul ID l’emportera toujours sur 100 classes, parce que le nombre de classes est sans importance s’il existe un ID.

Remarque : ces calculs ne s’appliquent qu’aux styles définis en CSS. Les styles définis en ligne auront la priorité.

 NdT : Pour approfondir ce sujet, vous pouvez consulter Cascade CSS et priorité des sélecteurs de Laurent Denis et IDs et classes CSS de Louis Lazaris.

Et :not là-dedans ?

La pseudo-classe de négation n’ajoute rien à la quantité de spécificité contrairement aux autres pseudo-classes. Cependant, les sélecteurs situés à l’intérieur de :not, oui.

Relativement à la spécificité, ajouter p:not(.foo) revient au même qu’ajouter .notFoo à tous les p n’ayant pas la classe .foo.

Par exemple :

p.bar { color: red; }  
p:not(.foo) { color: green; }  

De quelle couleur pensez-vous que sera <p class="bar"> ? La bonne réponse est vert, pas rouge. Ceci parce qu’en ajoutant la règle :not nous avons fait l’équivalent d’un ajout de classe .notFoo à notre élément <p class="bar">.

SélecteurNb d’IDsNb de ClassesNb de TypesSpécificitéVainqueur
p.bar0110-1-1Égalité
p:not(.foo)01 10-1-1Égalité

La règle :not est maintenant au même niveau de spécificité que les éléments ayant une “vraie” classe. Mais comme elle est définie après dans le CSS, c’est elle qui prévaut.Cela explique aussi pourquoi, dans notre exemple de nav, la règle :not l’emportait sur les sélecteurs de types imbriqués.

SélecteurNb d’IDsNb de ClassesNb de TypesSpécificitéVainqueur
a:not(.ul)0110-1-1
nav a0020-0-2 

Les choses deviennent encore plus complexes quand on introduit des IDs. De même que p:not(.foo) revient à peu près à ajouter .notFoo à tous les éléments <p>, de même p:not(#bar) revient à ajouter #notBar à tous les éléments <p>. Par exemple si nous avons :

p:not(#foo) { color: green; }  
p.bar { color: red; }  

...de quelle couleur pensez-vous que sera <p class="bar">? La bonne réponse est vert, pas rouge.

SélecteurNb d’IDsNb de ClassesNb de TypesSpécificitéVainqueur
p.bar0110-1-1 
p:not(#foo)1011-0-1

Bien que notre élément positif et sa classe soient déclarés après dans le CSS, nous avons ajouté pour ainsi dire un ID à l’élément à partir de la règle :not !

Utiliser :not

Cet effet de :not m’a amenée à repensé la façon dont je l’utilisais. Nous croyons contourner le besoin d’écrire des règles spécifiques pour en écraser d’autres, comme dans notre premier exemple, mais en réalité :not nous amène à faire quelque chose qui y ressemble, sans que nous nous en apercevions.

Je vais continuer à utiliser :not parce que bien souvent c’est la façon la plus propre d’écrire, mais je le ferai en tenant compte de quelques avertissements :

  • Ne jamais l’utiliser avec des IDs, par exemple : :not(#bar)
  • Limiter son utilisation aux sélecteurs de type génériques, par exemple div dans div:not(.foo)
  • Définir les règles :not le plus en amont possible dans mon CSS de façon à pouvoir les écraser si nécessaire.

Intéressé par CSS ? Retrouvez une liste des meilleurs articles et ressources du web.

Tous les articles sur CSS parus dans la Cascade.

Du même auteur, dans la Cascade :

Comment fonctionne CSS float
Comment fonctionne z-index
Contrôler le modèle de boîte
Cacher des éléments avec CSS
Styler les images non affichées
Sur :not et la spécificité
CSS Grid et le Saint Graal
CSS Initial, Inherit, Unset et Revert

Articles sur les mêmes thèmes dans la Cascade :

La pseudo-classe de négation
IDs et classes
Combinateurs et pseudo-classes CSS
Dico CSS : la pseudo-classe :not(s)


Article original paru le 15 mars 2016 dans Bits of Code.

Sur l’auteur : est UI Designer et Front-End Developer à Lagos, au Nigéria. Son blog Bits of Code s’adresse aux développeurs autodidactes comme elle, elle y explore la théorie cachée derrière les morceaux de code que nous écrivons, afin de mieux comprendre le développement web. On peut la suivre sur Twitter ou sur Github.

Traduit avec l’aimable autorisation de Bits of Code et de l’auteur.
Copyright Bits of Code © 2016.