Leçon 2/8 8 min

Sélecteurs et propriétés

Ciblez précisément les éléments à styliser avec les sélecteurs CSS et maîtrisez la spécificité.

Les types de sélecteurs

Un sélecteur CSS désigne les éléments HTML à styliser. Il en existe trois principaux :

  • Sélecteur d'élément : cible toutes les balises d'un type : h1, p, a
  • Sélecteur de classe : cible les éléments avec une classe donnée : .card, .btn-primary
  • Sélecteur d'id : cible un élément unique : #header, #menu

En pratique, on utilise surtout les classes (90% du temps). Les id sont réservés aux cas uniques.

Les combinateurs

Les combinateurs permettent de cibler des éléments selon leur position dans l'arbre HTML :

  • div p : descendant : tous les <p> à l'intérieur d'un <div>, même imbriqués
  • div > p : enfant direct : seulement les <p> directement enfants du <div>
  • h2 + p : frère adjacent : le <p> juste après un <h2>

Les états et les fantômes : pseudo-classes et pseudo-éléments

Les sélecteurs que vous connaissez ciblent des éléments selon ce qu'ils sont. Les pseudo-classes ciblent des éléments selon ce qu'ils font, leur état au moment précis où le navigateur applique le style.

Les pseudo-classes : l'état d'un élément

Une pseudo-classe s'écrit avec un seul deux-points (:) après le sélecteur :

  • a:hover : le lien quand la souris est dessus
  • input:focus : le champ sélectionné au clavier ou au clic
  • tr:nth-child(odd) : une ligne sur deux dans un tableau (zébrage)
  • li:first-child / li:last-child : premier ou dernier élément de la liste

✦ Accessibilité clavier : Ne retirez jamais l'outline de :focus sans le remplacer par un style visible. outline: none seul rend le site inutilisable au clavier (personnes en situation de handicap, utilisateurs avancés). Remplacez-le par quelque chose de clair : outline: 2px solid #2b6cb0; outline-offset: 2px.

Les pseudo-éléments : des fantômes créés par CSS

Un pseudo-élément s'écrit avec deux deux-points (::). Il crée un élément virtuel que vous ne tapez pas dans le HTML : le navigateur l'insère pour vous.

  • a::before : insère quelque chose avant le contenu du lien
  • a::after : insère quelque chose après

La propriété content est obligatoire pour qu'ils apparaissent :

/* Le ► qui précède chaque lien de navigation */
.nav-link::before {
  content: "► ";
  color: #2b6cb0;
}

/* Un badge "Nouveau" inséré via CSS */
.card--new::after {
  content: "Nouveau";
  background: #329e5a;
  color: #fff;
  font-size: 0.75rem;
  padding: 2px 6px;
  border-radius: 4px;
  margin-left: 8px;
}

Vous avez déjà croisé *::before dans la leçon sur le box model. C'est le même mécanisme : on cible le pseudo-élément de chaque élément de la page (* = tout) pour réinitialiser le box-sizing globalement.

💡 Une lettre d'histoire : La notation avec un seul : (comme :before) vient de CSS 2. CSS 3 a introduit le double :: pour distinguer pseudo-classes (un seul :) et pseudo-éléments (deux ::). Les navigateurs acceptent encore :before, mais écrivez toujours ::before.

Dernier point avant la prochaine section : une pseudo-classe compte comme une classe dans le calcul de spécificité. a:hover poids 0-1-1, exactement comme a.active.

Essayez vous-même

Le « Cascading » : la cascade

Le C de CSS signifie Cascading (cascade). Quand plusieurs règles ciblent le même élément, le navigateur les applique dans un ordre de priorité, comme une cascade d'eau où chaque couche recouvre la précédente :

  • 1. Styles du navigateur : les styles par défaut (les titres sont gros, les liens sont bleus)
  • 2. Votre CSS : vos règles écrasent les styles par défaut
  • 3. Ordre d'apparition : si deux règles identiques, la dernière gagne
  • 4. Spécificité : une règle plus spécifique l'emporte (voir ci-dessous)
Échelle de priorité de la cascade CSS : du moins prioritaire (styles navigateur) au plus prioritaire (spécificité), la couche du haut l'emporte. PRIORITÉ 4 Spécificité la règle la plus ciblée l'emporte ✓ gagne 3 Ordre d'apparition à égalité, la dernière règle l'emporte 2 Votre CSS écrase les styles par défaut 1 Styles du navigateur valeurs par défaut (titres gros, liens bleus)
Plus on monte, plus la règle est prioritaire : la couche du haut recouvre les autres.

C'est pour ça que l'ordre de vos fichiers CSS compte, et que mettre un style en bas du fichier peut « écraser » celui du haut.

La spécificité

Prédisez avant de lire

Avant de dérouler : Ces deux règles s'appliquent au même lien (#menu a { color: blue; } et .lien-rouge { color: red; }), et .lien-rouge est écrite après dans le fichier. De quelle couleur sera le lien, et pourquoi l'ordre d'écriture ne suffit-il pas à trancher ?

Voir la réponse

Le lien sera bleu. Ce n'est pas l'ordre d'écriture qui tranche ici, mais la spécificité : #menu a contient un ID, ce qui lui donne un poids bien plus élevé que la classe .lien-rouge. La hiérarchie : style en ligne > ID > classe > élément. L'ordre du fichier ne départage que les règles de spécificité égale ; ici les spécificités sont différentes, donc #menu a gagne, peu importe où il est écrit. C'est pour ça qu'on évite les ID en CSS : ils sont très difficiles à surcharger.

La spécificité est le système de points qui décide quelle règle gagne quand plusieurs ciblent le même élément :

  • Sélecteur d'élément (p) : spécificité faible
  • Classe (.card) : spécificité moyenne
  • Id (#header) : spécificité forte
  • Inline (style="...") : spécificité très forte
  • !important : écrase tout (mais c'est une mauvaise pratique)
Les 4 niveaux de spécificité du plus fort (inline) au plus faible (élément), et l'exemple #nav .menu a décompté 0·1·1·1. plus fort → plus faible inline id classe élément #nav .menu a 0 1 1 1 · · · inline 1 id 1 classe 1 élément on compare de gauche à droite
À gauche le plus fort : on compare colonne par colonne, de gauche à droite.

Règle d'or : Ne jamais utiliser !important sauf cas de force majeure. Si vous en avez besoin, c'est que la structure de vos sélecteurs est à revoir.

Propriétés courantes

  • color : couleur du texte
  • background-color : couleur de fond
  • font-family : police de caractères
  • font-size : taille du texte
  • font-weight : graisse (normal, bold, 600...)
  • text-align : alignement (left, center, right)
  • text-decoration : soulignement, barré... (none, underline)

Les variables CSS : définir une valeur une fois

Imaginez que votre charte graphique utilise un bleu partout : dans les boutons, les titres, les bordures des cartes. Vous avez écrit #2b6cb0 quarante fois dans votre fichier. Le client rappelle : il veut du violet. Vous passez une heure à chercher-remplacer et vous en ratez trois. Ce scénario, les variables CSS (propriétés personnalisées) l'éliminent.

Une variable CSS se déclare avec deux tirets devant son nom, le plus souvent sur le sélecteur :root qui couvre tout le document :

:root {
  --primary: #2b6cb0;
  --gap: 16px;
}

/* On lit la valeur avec var() */
.btn {
  background-color: var(--primary);
  padding: var(--gap);
}

.card {
  border: 2px solid var(--primary);
  margin-bottom: var(--gap);
}

/* Redéfinition locale : une carte "danger" a sa propre couleur */
.card--danger {
  --primary: #c0392b;   /* écrase --primary pour ce sélecteur et ses descendants */
  border-color: var(--primary);
}

On peut aussi fournir une valeur de repli : var(--ma-couleur, black) utilise black si --ma-couleur n'est pas définie. Pratique pour les composants réutilisables dans des contextes variés.

Ce qui rend les variables CSS plus puissantes que les variables Sass ou Less, c'est qu'elles vivent dans le navigateur, à l'exécution. Elles suivent la cascade et l'héritage : déclarées sur :root elles valent partout, mais on peut les redéfinir sur n'importe quel sélecteur, dans une media query ou même via JavaScript. Un préprocesseur Sass calcule ses variables à la compilation et produit du CSS statique ; une variable CSS peut changer en direct, sans recompiler.

✦ Bonne pratique : Centralisez toutes vos couleurs de marque, tailles de police récurrentes et espacements sur :root sous forme de variables CSS. Pour changer de thème, vous n'aurez qu'à modifier quelques lignes dans ce bloc, au lieu de traquer chaque occurrence dans vos règles.

Types of selectors

A CSS selector designates which HTML elements to style. There are three main types:

  • Element selector — targets all tags of a type: h1, p, a
  • Class selector — targets elements with a given class: .card, .btn-primary
  • ID selector — targets a unique element: #header, #menu

In practice, you'll use classes 90% of the time. IDs are reserved for unique cases.

Combinators

Combinators let you target elements based on their position in the HTML tree:

  • div pdescendant: all <p> inside a <div>, even nested
  • div > pdirect child: only <p> directly inside the <div>
  • h2 + padjacent sibling: the <p> right after an <h2>

States and ghosts: pseudo-classes and pseudo-elements

The selectors you know target elements based on what they are. Pseudo-classes target elements based on what they are doing, their state at the exact moment the browser applies the style.

Pseudo-classes: an element's state

A pseudo-class is written with a single colon (:) after the selector:

  • a:hover — the link while the mouse is over it
  • input:focus — the field selected by keyboard or click
  • tr:nth-child(odd) — every other row in a table (striping)
  • li:first-child / li:last-child — first or last item in a list

✦ Keyboard accessibility: Never remove the :focus outline without replacing it with a visible style. outline: none alone makes the site unusable by keyboard (people with disabilities, power users). Replace it with something clear: outline: 2px solid #2b6cb0; outline-offset: 2px.

Pseudo-elements: ghosts created by CSS

A pseudo-element is written with two colons (::). It creates a virtual element you don't type in the HTML — the browser inserts it for you.

  • a::before — inserts something before the link's content
  • a::after — inserts something after

The content property is required for them to appear:

/* The ► in front of every nav link */
.nav-link::before {
  content: "► ";
  color: #2b6cb0;
}

/* A "New" badge inserted via CSS */
.card--new::after {
  content: "New";
  background: #329e5a;
  color: #fff;
  font-size: 0.75rem;
  padding: 2px 6px;
  border-radius: 4px;
  margin-left: 8px;
}

You already saw *::before in the box model lesson. Same mechanism: it targets the pseudo-element of every element on the page (* = all) to reset box-sizing globally.

💡 A bit of history: The single-colon notation (like :before) comes from CSS 2. CSS 3 introduced the double :: to distinguish pseudo-classes (one colon) from pseudo-elements (two colons). Browsers still accept :before, but always write ::before.

One last point before the next section: a pseudo-class counts as a class in the specificity calculation. a:hover weighs 0-1-1, exactly like a.active.

The "Cascading" — the cascade

The C in CSS stands for Cascading. When multiple rules target the same element, the browser applies them in a priority order, like layers of water where each one overrides the previous:

  • 1. Browser defaults — built-in styles (headings are big, links are blue)
  • 2. Your CSS — your rules override defaults
  • 3. Source order — if two identical rules exist, the last one wins
  • 4. Specificity — a more specific rule wins (see below)
CSS cascade priority ladder: from lowest (browser styles) to highest (specificity); the top layer wins. PRIORITY 4 Specificity the most targeted rule wins ✓ wins 3 Order of appearance on a tie, the last rule wins 2 Your CSS overrides the default styles 1 Browser styles defaults (big headings, blue links)
The higher the layer, the higher its priority: the top one overrides the rest.

That's why the order of your CSS files matters, and why a style at the bottom of your file can "override" one at the top.

Specificity

Predict before reading

Before you scroll down: Two rules target the same link — #menu a { color: blue; } and .lien-rouge { color: red; } — and .lien-rouge appears later in the file. What color will the link be, and why doesn't source order settle it?

See the answer

The link will be blue. Source order doesn't settle it here — specificity does. #menu a contains an ID, which carries far more weight than the class .lien-rouge. The hierarchy: inline style > ID > class > element. Source order only breaks ties between rules of equal specificity; here the specificities differ, so #menu a wins regardless of where it appears in the file. That's why ID selectors are avoided in CSS: they are very hard to override.

Specificity is the point system that decides which rule wins when multiple target the same element:

  • Element selector (p) — low specificity
  • Class (.card) — medium specificity
  • ID (#header) — high specificity
  • Inline (style="...") — very high specificity
  • !important — overrides everything (but it's a bad practice)
The 4 specificity levels from strongest (inline) to weakest (element), and the example #nav .menu a counted as 0·1·1·1. strongest → weakest inline id class element #nav .menu a 0 1 1 1 · · · inline 1 id 1 class 1 element compare from left to right
Strongest on the left: you compare column by column, left to right.

Golden rule: Never use !important unless absolutely necessary. If you need it, your selector structure needs rethinking.

Common properties

  • color — text color
  • background-color — background color
  • font-family — font family
  • font-size — text size
  • font-weight — weight (normal, bold, 600...)
  • text-align — alignment (left, center, right)
  • text-decoration — underline, strikethrough... (none, underline)

CSS variables: define a value once

Imagine your design system uses one blue everywhere: buttons, headings, card borders. You've written #2b6cb0 forty times across your file. The client calls: they want purple now. You spend an hour doing find-and-replace and miss three occurrences. CSS variables (custom properties) kill that scenario entirely.

A CSS variable is declared with two dashes before its name, most often on the :root selector, which covers the entire document:

:root {
  --primary: #2b6cb0;
  --gap: 16px;
}

/* Read the value with var() */
.btn {
  background-color: var(--primary);
  padding: var(--gap);
}

.card {
  border: 2px solid var(--primary);
  margin-bottom: var(--gap);
}

/* Local override: a "danger" card has its own color */
.card--danger {
  --primary: #c0392b;   /* overrides --primary for this selector and its descendants */
  border-color: var(--primary);
}

You can also provide a fallback value: var(--my-color, black) uses black if --my-color is not defined. Handy for reusable components dropped into varied contexts.

What makes CSS variables more powerful than Sass or Less variables is that they live in the browser, at runtime. They follow the cascade and inheritance: declared on :root they apply everywhere, but you can redefine them on any selector, inside a media query, or even via JavaScript. A Sass preprocessor computes its variables at compile time and outputs static CSS; a CSS variable can change on the fly, no recompilation needed.

✦ Best practice: Centralise all your brand colors, recurring font sizes and spacing values on :root as CSS variables. To switch themes, you only need to update a handful of lines in that one block, rather than hunting down every occurrence across your rules.

Essayez vous-même

🎯 Pratique

S'entraîner (clique pour ouvrir) :

Prompt IA
Avec l'IA

Copiez ce prompt dans Claude ou ChatGPT :

Donne-moi 5 exemples de sélecteurs CSS du plus simple au plus complexe, avec une explication de la spécificité de chacun et un cas d'utilisation concret.
💬 Ré-explique sans regarder
Ré-explique sans regarder

Sans relire la réponse de l'IA : avec tes mots, comment décides-tu lequel de .menu a et #nav a l'emporte ?

Une bonne explication compte chaque sélecteur par catégorie : #nav a vaut 1 id + 1 élément, .menu a vaut 1 classe + 1 élément. On compare colonne par colonne de gauche à droite : la colonne id (1 contre 0) tranche tout de suite, donc #nav a gagne, quel que soit l'ordre dans le fichier. Un id pèse plus lourd que n'importe quel nombre de classes.
Exercice : Corrigez le code de l'IA

L'IA a abusé de !important partout. Corrigez la spécificité en utilisant des classes au lieu de !important.

Corrigez le code
⚖️ Juge le code de l'IA
Accepter ou rejeter le code de l'IA

Tu demandes à l'IA de styliser le lien actif du menu. Voici sa réponse. Ton rôle de relecteur : l'accepter telle quelle ou la rejeter, et dire pourquoi.

#sidebar nav ul.menu li.item a.link.active {
  color: red;
}
À rejeter. Le rendu est correct, mais ce sélecteur est sur-spécifié : il empile un id, deux classes de structure et toute la chaîne d'ancêtres pour viser un seul lien. Sa spécificité devient énorme (1 id + 4 classes + 1 élément), donc le jour où tu voudras le surcharger ailleurs, tu seras obligé de surenchérir, voire d'en venir au !important qu'on cherche justement à éviter. Et il casse dès que tu déplaces le menu hors du #sidebar. Le réflexe pro : une seule classe ciblée, par exemple .menu-link.active, suffit et reste maintenable.
🧠 Rappel libre
Rappel libre

Sans remonter dans la leçon : que ciblent respectivement .card, #card et card, et lequel des trois a la spécificité la plus forte ?

.card (avec un point) cible tous les éléments ayant la classe card ; #card (avec un dièse) cible l'unique élément dont l'id vaut card ; card (sans rien) cible une balise <card>, qui n'existe pas en HTML standard. C'est #card qui a la spécificité la plus forte : un id l'emporte sur une classe, qui l'emporte sur un élément.
Vous voulez styliser un bouton .btn uniquement quand il est sélectionné au clavier. Quel sélecteur utiliser ?
Dans l'ordre de spécificité (du plus faible au plus fort) :
Que fait le sélecteur div > p ?
Pourquoi éviter !important ?
Prochaine étape

Vous savez maintenant cibler n'importe quel élément. Reste à comprendre comment il occupe l'espace. Dans la prochaine leçon, on décortique le box model : contenu, padding, bordure et margin, ces quatre couches qui expliquent pourquoi un bloc prend la taille qu'il prend.

Leçon 3 : Le box model →

Une erreur dans cette leçon, un passage flou, une question ? Écrivez-moi : chaque retour améliore ce cours.

Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement