Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 1123 minDébutant

CSS moderne : variables, nesting, :has() et animations

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • centraliser couleurs et espacements avec des variables CSS ;
  • utiliser le nesting natif et les sélecteurs :is(), :where() et :has() ;
  • animer avec des transitions et des @keyframes ;
  • organiser une feuille de style et situer Bootstrap et Tailwind.

4 exercices corrigés

Le CSS moderne désigne les fonctionnalités arrivées ces dernières années dans tous les navigateurs, qui remplacent des outils autrefois indispensables (préprocesseurs comme Sass, scripts JavaScript) : les variables CSS, l'imbrication native (nesting), le sélecteur :has(), les propriétés logiques, les fonctions de calcul. Ce chapitre les présente avec les transitions et animations, puis donne une méthode pour organiser une feuille de style et un mot sur les frameworks Bootstrap et Tailwind.

Les variables CSS (custom properties)

Une propriété personnalisée, qu'on appelle couramment variable CSS, commence par deux tirets. On la déclare en général sur :root (l'élément <html>), pour qu'elle soit disponible partout, et on la lit avec var() :

css
:root {
  --couleur-principale: #0f766e;
  --couleur-texte: #1f2937;
  --couleur-fond: #ffffff;
  --rayon: 8px;
  --espace: 1rem;
}

.bouton {
  background-color: var(--couleur-principale);
  border-radius: var(--rayon);
  padding: calc(var(--espace) / 2) var(--espace);
}

h2 {
  color: var(--couleur-principale);
}

C'est la traduction directe d'une charte graphique : les couleurs et espacements de l'organisation sont déclarés une fois, en tête de feuille. Le jour où la médiathèque change de vert, une seule ligne change.

Les variables suivent la cascade et l'héritage : on peut les redéfinir localement, et tous les descendants prennent la nouvelle valeur.

css
.zone-jeunesse {
  --couleur-principale: #7c3aed;   /* dans l'espace jeunesse, boutons et titres passent en violet */
}

var() accepte une valeur de secours, utilisée si la variable n'est pas définie : var(--ombre, none).

Les variables rendent aussi très simple un thème sombre qui suit le réglage du système :

css
:root {
  color-scheme: light dark;   /* les champs et barres de défilement suivent aussi le thème */
  --couleur-texte: #1f2937;
  --couleur-fond: #ffffff;
  --couleur-principale: #0f766e;
}

@media (prefers-color-scheme: dark) {
  :root {
    --couleur-texte: #e5e7eb;
    --couleur-fond: #111827;
    --couleur-principale: #2dd4bf;
  }
}

body {
  color: var(--couleur-texte);
  background-color: var(--couleur-fond);
}

Le reste de la feuille n'utilise que les variables : il n'y a rien d'autre à écrire pour le mode sombre. La fonction light-dark(#1f2937, #e5e7eb), reconnue par tous les navigateurs actuels depuis 2024, permet même d'écrire les deux valeurs sur une seule ligne, à condition d'avoir déclaré color-scheme: light dark.

Le nesting CSS natif

Le CSS sait désormais imbriquer des règles, comme le faisaient les préprocesseurs Sass ou Less. Le caractère & représente le sélecteur parent :

css
.carte {
  padding: 1rem;
  border: 1px solid #64748b;

  & h3 {                      /* .carte h3 */
    margin-top: 0;
  }

  &:hover,
  &:focus-within {            /* .carte:hover, .carte:focus-within */
    border-color: var(--couleur-principale);
  }

  &.complet {                 /* .carte.complet (sans espace) */
    opacity: 0.7;
  }

  @media (width >= 40rem) {   /* une media query imbriquée */
    padding: 1.5rem;
  }
}

Le nesting est reconnu par tous les navigateurs actuels depuis fin 2023. Devant un sélecteur de type, le & est devenu facultatif (h3 { … } dans .carte donne .carte h3), mais l'écrire rend le code plus clair.

Les sélecteurs :is(), :where() et :has()

:is() et :where() regroupent des sélecteurs pour raccourcir l'écriture :

css
/* Au lieu de : article h2, article h3, aside h2, aside h3 */
:is(article, aside) :is(h2, h3) {
  line-height: 1.2;
}

/* Les listes qui ont une classe (menus, listes de cartes) : tout est dans :where(), spécificité nulle, facile à surcharger */
:where(ul[class], ol[class]) {
  list-style: none;
  padding: 0;
}

La différence est la spécificité (chapitre 7) : :is() prend celle de son argument le plus fort, :where() vaut zéro.

:has(), reconnu par tous les navigateurs actuels depuis fin 2023, a changé la façon d'écrire du CSS : il sélectionne un élément selon ce qu'il contient ou selon ce qui le suit. On l'a longtemps appelé le « sélecteur de parent » qui manquait au CSS.

css
/* Les cartes qui contiennent une image : disposition en deux colonnes */
.carte:has(img) {
  display: grid;
  grid-template-columns: 8rem 1fr;
}

/* Le champ et son étiquette en rouge si le champ est invalide */
.champ:has(input:user-invalid) label {
  color: #b91c1c;
}

/* Une option de choix encadrée quand sa case est cochée */
label:has(input:checked) {
  outline: 2px solid var(--couleur-principale);
}

/* Le titre suivi directement d'un paragraphe de chapeau */
h2:has(+ p.chapeau) {
  margin-bottom: 0.25rem;
}

Avant :has(), chacun de ces cas demandait une classe ajoutée à la main dans le HTML, ou un script.

Les propriétés logiques

Les propriétés « physiques » (margin-left, padding-top, width) parlent de gauche, droite, haut et bas. Les propriétés logiques parlent du sens de lecture :

  • l'axe inline est celui dans lequel on lit une ligne (horizontal en français) ;
  • l'axe block est celui dans lequel les paragraphes s'empilent (vertical en français) ;
  • start et end désignent le début et la fin dans ce sens (gauche et droite en français, droite et gauche en arabe ou en hébreu).
PhysiqueLogique
margin-left et margin-rightmargin-inline (ou margin-inline-start, margin-inline-end)
padding-top et padding-bottompadding-block
width, heightinline-size, block-size
top, right, bottom, left à 0inset: 0
text-align: lefttext-align: start
border-leftborder-inline-start
css
.contenu {
  max-inline-size: 70rem;
  margin-inline: auto;      /* centré horizontalement */
  padding-inline: 1rem;     /* de l'air à gauche et à droite */
}

.citation {
  border-inline-start: 4px solid var(--couleur-principale);  /* trait au début de la ligne */
  padding-inline-start: 1rem;
}

Si le site est un jour traduit dans une langue qui s'écrit de droite à gauche, ces règles s'adaptent seules ; margin-inline: auto est en plus plus court que margin-left: auto; margin-right: auto;.

calc(), min(), max() et clamp()

FonctionRôleExemple
calc()un calcul mêlant des unitéswidth: calc(100% - 2rem);
min()la plus petite des valeurswidth: min(100%, 40rem); : 40rem au plus, jamais plus large que le parent
max()la plus grandepadding: max(1rem, 3vw); : au moins 1rem
clamp()une valeur bornéefont-size: clamp(1rem, 0.9rem + 0.5vw, 1.25rem);

Dans calc(), les opérateurs + et - doivent être entourés d'espaces : calc(100% -2rem) est invalide.

Transitions et animations

Une transition anime le passage d'une valeur à une autre quand un état change (survol, focus, ajout d'une classe) :

css
.bouton {
  background-color: var(--couleur-principale);
  transition: background-color 200ms ease, transform 200ms ease;
}

.bouton:hover,
.bouton:focus-visible {
  background-color: #115e59;
  transform: translateY(-2px);
}

transition prend la propriété animée, la durée, et une courbe d'accélération (ease, linear, ease-in-out). Évite transition: all, qui anime aussi des propriétés que tu ne voulais pas animer.

La propriété transform déplace, agrandit ou fait tourner un élément sans modifier la mise en page autour de lui : translate(10px, 0), scale(1.05), rotate(45deg). Pour une animation fluide, anime de préférence transform et opacity : le navigateur les traite sans recalculer la mise en page. Animer width, height, top ou margin force ce recalcul à chaque image et peut saccader.

Une animation se décrit par étapes avec @keyframes, et se joue sans changement d'état :

css
@keyframes apparition {
  from {
    opacity: 0;
    transform: translateY(1rem);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.message-confirmation {
  animation: apparition 400ms ease-out;
}

Organiser sa feuille de style

Un projet de quelques pages tient dans un fichier style.css, organisé en sections commentées, du général au particulier :

css
/* 1. Variables (charte graphique) */
:root { … }

/* 2. Base : remise à zéro et éléments HTML */
*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: system-ui, sans-serif;
  line-height: 1.6;
  color: var(--couleur-texte);
  background-color: var(--couleur-fond);
}

img,
picture,
video,
svg {
  display: block;
  max-width: 100%;
  height: auto;
}

input,
button,
textarea,
select {
  font: inherit;
}

h1,
h2,
h3 {
  line-height: 1.2;
  text-wrap: balance;     /* équilibre les lignes des titres sur plusieurs lignes */
}

/* 3. Mise en page : en-tête, grille de la page, pied */
/* 4. Composants : carte, bouton, formulaire, menu */
/* 5. Utilitaires : .visuellement-masque, .texte-centre */

La section 2 est une remise à zéro (reset) moderne : quelques règles qui gomment les différences entre navigateurs et les comportements gênants par défaut.

Pour nommer les classes, une convention évite les collisions dans les projets à plusieurs. La plus répandue est BEM (Bloc, Élément, Modificateur) :

html
<article class="carte carte--complet">
  <h3 class="carte__titre">Initiation à l'ordinateur</h3>
  <p class="carte__date">Mercredi 3 juin, 10 h</p>
  <a class="carte__bouton" href="…">S'inscrire à l'initiation</a>
</article>
  • le bloc est le composant autonome : .carte ;
  • l'élément est une partie du bloc, après deux soulignés : .carte__titre ;
  • le modificateur est une variante, après deux tirets : .carte--complet.

Chaque sélecteur reste une classe unique, de spécificité (0,1,0) : la cascade se réduit à l'ordre d'écriture, et on sait d'un coup d'œil à quel composant appartient une classe.

Les frameworks CSS : Bootstrap et Tailwind

Un framework CSS fournit une feuille de style toute faite. Tu les croiseras en stage :

BootstrapTailwind CSS
Principedes composants prêts à l'emploi (.btn, .card, .navbar) et une grilledes classes utilitaires, une par propriété (p-4, text-center, bg-teal-700), combinées dans le HTML
Écriture<button class="btn btn-primary"><button class="rounded bg-teal-700 px-4 py-2 text-white">
Atoutrapide pour un back-office ou un prototype, documentation abondanteaucune feuille CSS à écrire, une interface cohérente, seules les classes utilisées sont générées
Limitetous les sites Bootstrap se ressemblent ; il faut surcharger pour personnaliserle HTML se charge de longues listes de classes ; une étape de construction est nécessaire

Exercices

Exercice 1 sur 4 Passer à des variables Réussi À revoir

Réécris ce CSS avec des variables déclarées sur :root, puis ajoute un thème sombre qui suit le réglage du système (fond #111827, texte #e5e7eb, vert #2dd4bf).

css
body { color: #1f2937; background: #ffffff; }
h2 { color: #0f766e; }
.bouton { background: #0f766e; color: #ffffff; }
a { color: #0f766e; }
Voir la réponseMasquer la réponse de l'exercice 1
css
:root {
  color-scheme: light dark;
  --texte: #1f2937;
  --fond: #ffffff;
  --principale: #0f766e;
  --sur-principale: #ffffff;
}

@media (prefers-color-scheme: dark) {
  :root {
    --texte: #e5e7eb;
    --fond: #111827;
    --principale: #2dd4bf;
    --sur-principale: #111827;
  }
}

body { color: var(--texte); background: var(--fond); }
h2 { color: var(--principale); }
.bouton { background: var(--principale); color: var(--sur-principale); }
a { color: var(--principale); }

Le texte du bouton a besoin de sa propre variable : du blanc sur le vert clair #2dd4bf serait illisible (contraste de 1,9:1 environ), alors que le fond sombre #111827 sur ce vert atteint 9,5:1.

Tu avais trouvé ?

Exercice 2 sur 4 Traduire en nesting Réussi À revoir

Réécris ces règles avec le nesting natif, dans un seul bloc .menu.

css
.menu { display: flex; gap: 1rem; }
.menu a { text-decoration: none; }
.menu a:hover, .menu a:focus-visible { text-decoration: underline; }
.menu a[aria-current="page"] { font-weight: 700; }
@media (width < 40rem) { .menu { flex-direction: column; } }
Voir la réponseMasquer la réponse de l'exercice 2
css
.menu {
  display: flex;
  gap: 1rem;

  & a {
    text-decoration: none;

    &:hover,
    &:focus-visible {
      text-decoration: underline;
    }

    &[aria-current="page"] {
      font-weight: 700;
    }
  }

  @media (width < 40rem) {
    flex-direction: column;
  }
}

&:hover dans & a donne .menu a:hover : le & collé à la pseudo-classe ne crée pas d'espace. Le résultat a exactement les mêmes sélecteurs et la même spécificité que la version d'origine.

Tu avais trouvé ?

Exercice 3 sur 4 Écrire avec :has() Réussi À revoir

Écris un sélecteur pour chaque besoin.

  1. Mettre une bordure rouge à un <fieldset> qui contient au moins un champ invalide après saisie.
  2. Masquer le paragraphe .aucun-resultat d'une liste .resultats quand cette liste contient au moins un <li>.
  3. Donner un fond coloré à la ligne de tableau <tr> dont la case à cocher est cochée.
  4. Agrandir le <main> quand la page n'a pas d'<aside> (le <body> contient main et éventuellement aside).
Voir la réponseMasquer la réponse de l'exercice 3
  1. fieldset:has(:user-invalid) { border-color: #b91c1c; }
  2. .resultats:has(li) .aucun-resultat { display: none; } (si le paragraphe est dans .resultats) ; s'il le suit, .resultats:has(li) + .aucun-resultat.
  3. tr:has(input[type="checkbox"]:checked) { background-color: #ccfbf1; }
  4. body:not(:has(aside)) main { max-inline-size: none; } (ou une autre règle de largeur) : :not() et :has() se combinent.

Tu avais trouvé ?

Exercice 4 sur 4 Une animation respectueuse Réussi À revoir

Écris le CSS d'un bouton qui, au survol et au focus clavier, s'agrandit de 5 % en 150 ms, sans animer la mise en page. Ajoute ce qu'il faut pour les personnes qui demandent moins d'animations.

Voir la réponseMasquer la réponse de l'exercice 4
css
.bouton {
  transition: transform 150ms ease;
}

.bouton:hover,
.bouton:focus-visible {
  transform: scale(1.05);
}

@media (prefers-reduced-motion: reduce) {
  .bouton {
    transition: none;
  }
}

transform: scale() agrandit le bouton sans pousser ses voisins ni recalculer la mise en page. Avec la réduction des animations demandée, le bouton s'agrandit toujours, mais sans mouvement progressif. Tu peux aussi écrire la règle à l'envers : @media (prefers-reduced-motion: no-preference) { .bouton { transition: … } }, l'animation n'étant alors ajoutée que pour ceux qui ne demandent rien.

Tu avais trouvé ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Accessibilité web : RGAA et WCAG en pratiqueRevoir le sommaire du cours