frontend / css
Sélecteurs et spécificité
Explication
Un peu d'histoire
CSS est proposé en 1994 par Håkon Wium Lie (alors au CERN, comme Tim Berners-Lee) avec Bert Bos, puis standardisé par le W3C en 1996 sous le nom CSS1. Avant CSS, la mise en forme d'une page se faisait directement dans le HTML (des balises <font>, des attributs color un peu partout), ce qui rendait les pages lourdes et très difficiles à faire évoluer. L'idée centrale de CSS est de séparer clairement le contenu (HTML) de sa présentation (CSS), pour pouvoir changer l'apparence d'un site entier sans toucher à sa structure.
Pourquoi apprendre CSS aujourd'hui
Sans CSS, le web ressemblerait à un document texte des années 90 : pas de mise en page, pas de couleurs personnalisées, pas d'adaptation à la taille de l'écran. CSS est indispensable pour créer des interfaces modernes, agréables et responsives (qui s'adaptent au mobile comme au grand écran). C'est aussi un langage qui évolue vite : des fonctionnalités comme Grid, Flexbox ou les animations ont considérablement changé la façon de concevoir des mises en page ces dernières années, donc c'est une compétence à la fois fondamentale et en perpétuelle évolution.
Ce que vous allez apprendre
- Choisir le bon sélecteur (élément, classe, id, attribut, pseudo-classe) selon le besoin
- Calculer la spécificité d'un sélecteur pour prédire quelle règle l'emportera
- Distinguer le cas où l'ordre d'écriture compte de celui où seule la spécificité compte
- Comprendre pourquoi
!importantdoit rester une exception, pas un réflexe - Utiliser
:is()et:where()pour écrire des sélecteurs groupés avec une spécificité maîtrisée
Dans quel contexte ?
Pendant une revue de code sur styles.css, un développeur ajoute une règle .carte h2.titre { color: green; } pour changer la couleur d'un titre, mais rien ne change à l'écran malgré un rechargement forcé. En inspectant l'élément dans les DevTools, il découvre une règle plus ancienne #header .titre { color: blue; } qui l'emporte, simplement parce qu'un #id pèse plus qu'une classe dans le calcul de spécificité — peu importe laquelle des deux règles a été écrite en dernier.
Étape 1 : un sélecteur répond à une seule question
Un sélecteur CSS sert à répondre à une question toute simple : "à quels éléments cette règle doit-elle s'appliquer ?". On peut cibler une balise entière, une classe partagée par plusieurs éléments, un identifiant unique, ou même un état précis comme "cet élément est survolé". Sans sélecteur précis, impossible de styliser exactement ce qu'on veut.
Étape 2 : le problème arrive avec plusieurs règles
Tant qu'une seule règle cible un élément, tout va bien. Le problème commence quand PLUSIEURS règles CSS visent le même élément avec des valeurs différentes pour la même propriété : le navigateur doit alors choisir laquelle appliquer.
Étape 3 : la solution, un système de poids
Voici comment le navigateur tranche : chaque sélecteur reçoit un "poids" appelé spécificité, calculé selon sa composition. Un id pèse plus qu'une classe, qui pèse elle-même plus qu'un simple élément — un peu comme un ordre venant du quartier général qui l'emporte toujours sur un ordre venant d'un simple service, même si ce dernier a été donné plus tard.
| Type de sélecteur | Exemple | Poids (id, classes, éléments) |
|---|---|---|
| Élément | p | (0, 0, 1) |
| Classe / attribut / pseudo-classe | .titre, [type], :hover | (0, 1, 0) |
| Id | #header | (1, 0, 0) |
| Style inline | style="..." | Toujours au-dessus, sauf !important |
Prérequis
Cette leçon suppose que vous savez déjà écrire une règle CSS de base (sélecteur { propriété: valeur; }) et l'associer à une page HTML.
Étape 4 : le piège n°1, croire que l'ordre décide
Il reste une confusion très répandue à corriger tout de suite : croire que "la dernière règle écrite gagne toujours". C'est faux. Un #header .titre écrit tout en haut du fichier bat toujours un .carte h2.titre écrit juste en dessous, même si ce dernier semble "plus récent" dans le code.
Étape 5 : quand l'ordre compte vraiment
Ce n'est que dans un cas précis que l'ordre d'écriture tranche : quand deux sélecteurs ont EXACTEMENT la même spécificité. Dans ce cas seulement, la dernière règle déclarée dans le fichier l'emporte.
Étape 6 : le réflexe à éviter, !important
Face à un style récalcitrant, le réflexe du débutant est souvent d'ajouter !important, qui contourne complètement le calcul de spécificité. Le problème : chaque !important supplémentaire ne peut être surchargé que par un autre !important, ce qui crée une escalade sans fin dans un projet qui grandit.
Piège fréquent
Utiliser !important pour "forcer" un style qui ne s'applique pas est souvent un symptôme, pas une solution : le vrai problème est presque toujours une spécificité mal maîtrisée ailleurs dans le fichier. Corrigez la cause (simplifiez ou ciblez mieux les sélecteurs concurrents) plutôt que de masquer le symptôme.
Et ensuite ?
Mieux vaut comprendre et corriger la spécificité en amont plutôt que forcer avec !important. Une fois les sélecteurs maîtrisés, la prochaine étape logique consiste à comprendre comment chaque élément occupe de l'espace sur la page, avec le box model.
Commandes & code
Sélecteurs et spécificité
Comment CSS choisit quelle règle gagne quand plusieurs s'appliquent au même élément.
/* Sélecteurs de base */
* { box-sizing: border-box; } /* universel */
p { color: #222; } /* type/élément */
.carte { padding: 16px; } /* classe */
#header { position: sticky; } /* id */
[data-state="ouvert"] { display: block; } /* attribut */
/* Combinateurs */
.carte p { margin: 0; } /* descendant : tous les p sous .carte */
.carte > p { margin: 0; } /* enfant direct uniquement */
h2 + p { font-weight: bold; } /* frère adjacent : le p juste après un h2 */
h2 ~ p { color: gray; } /* frères suivants : tous les p après h2 (même parent) */
/* Groupement */
h1, h2, h3 { font-family: "Inter", sans-serif; }/* Pseudo-classes structurelles */
li:first-child { font-weight: bold; }
li:last-child { border-bottom: none; }
li:nth-child(2n) { background: #f5f5f5; } /* lignes paires */
li:nth-child(3n + 1) { color: blue; } /* 1, 4, 7, 10... */
li:nth-last-child(1) { border: none; }
p:only-child { text-align: center; }
/* Pseudo-classes d'état */
a:hover { text-decoration: underline; }
a:visited { color: purple; }
input:focus { outline: 2px solid blue; }
input:disabled { opacity: 0.5; }
input:checked + label { font-weight: bold; }
li:empty { display: none; }
/* Pseudo-éléments */
p::first-line { font-weight: bold; }
p::first-letter { font-size: 2em; }
.tooltip::before { content: "Info : "; }
.champ::placeholder { color: #999; }/*
Calcul de spécificité : (inline, id, classes/attr/pseudo-classes, éléments)
élément -> (0, 0, 0, 1)
.classe -> (0, 0, 1, 0)
#id -> (0, 1, 0, 0)
style="..." -> (1, 0, 0, 0)
!important -> gagne TOUJOURS (à éviter, casse la cascade)
*/
.carte .titre { color: red; } /* (0,0,2,0) */
#header .titre { color: blue; } /* (0,1,1,0) -> GAGNE : id > classes */
.carte h2.titre { color: green; } /* (0,0,2,1) -> perd face à l'id malgré 2 classes */
/* :is() ne compte QUE le sélecteur le plus spécifique de sa liste */
:is(.carte, #header) p { color: red; } /* spécificité = celle de #header */
/* :where() a une spécificité de ZÉRO, quel que soit son contenu -> utile
pour poser des styles de base facilement surchargeables */
:where(.carte, #header) p { color: red; } /* spécificité = (0,0,0,0) */Résumé
- L'ordre de spécificité : inline > id > classes/attributs/pseudo-classes > éléments.
- À spécificité égale, la dernière règle déclarée dans le CSS gagne.
:where()a une spécificité nulle : pratique pour des styles de base facilement surchargés.!importantcasse la cascade normale ; à réserver aux cas exceptionnels (utilitaires, overrides ciblés).
Exercices pratiques
Mission : dénouer un conflit de spécificité sur une navbar
Objectif : Calculer la spécificité de plusieurs règles concurrentes pour prédire et corriger l'affichage réel d'un lien de menu.
Contexte
Sur styles.css, trois règles ciblent le même lien de menu, qui a pour attributs class="item actif visible" et se trouve à l'intérieur d'un conteneur id="widgets" :
.sidebar .item { color: gray; }
#widgets .item.actif { color: blue; }
.widget .item.actif.visible { color: green; }Le lien correspond aux trois sélecteurs à la fois. Avant d'ouvrir les DevTools, calcule toi-même laquelle de ces règles va réellement s'appliquer.