Fondamentaux du CSS
© Nicolas Lamy - Tous droits réservés.
Par Nicolas Lamy
Mise à jour du 6 Février 2026
Le CSS (Cascading Style Sheets) est un langage fondamental du web. Il intervient systématiquement dans la création de sites internet modernes, quels que soient leur taille, leur objectif ou leur public. Avant d’apprendre à écrire du CSS, il est indispensable de comprendre pourquoi ce langage existe, ce qu’il fait exactement, et comment il s’articule avec le HTML et le navigateur.
Cette première partie pose des bases solides, conceptuelles et techniques, afin d’éviter les erreurs classiques des débutants : mélange des rôles, duplication de styles, code difficilement maintenable.
Le CSS est un langage informatique dédié exclusivement à la présentation visuelle des documents HTML. Il ne permet pas de créer du contenu, mais d’en définir l’apparence.
Le CSS est un langage déclaratif.
Cela signifie que vous ne donnez pas des instructions étape par étape (comme dans un langage de programmation), mais que vous déclarez des règles visuelles que le navigateur va appliquer automatiquement.
Exemple simple :
p {
color: blue;
}
Ce code signifie :
p)Le CSS permet de gérer :
À ce stade, il est essentiel de retenir une idée centrale :
Le CSS ne donne jamais de sens au contenu.
Il ne fait que définir son apparence.
Le développement web repose sur une séparation claire des rôles :
Exemple de HTML sans CSS :
<h1>Titre principal</h1>
<p>Paragraphe de texte</p>
Ce code décrit :
Il ne dit rien sur :
C’est volontaire. Le HTML doit rester neutre visuellement.
À l’inverse, le CSS ne doit jamais :
Sans CSS :
Le CSS permet :
Dans un projet professionnel, aucun site n’existe sans CSS.
Le CSS n’est pas apparu tel qu’on le connaît aujourd’hui. Il a évolué progressivement pour répondre aux besoins du web.
À l’origine, le web proposait très peu de possibilités de mise en forme. Les premières versions du CSS permettaient uniquement :
Ces limitations expliquent pourquoi, historiquement, le HTML contenait parfois des informations visuelles. Le CSS est né pour corriger cette dérive.
Avec le temps, le CSS s’est enrichi :
Chaque évolution vise un objectif clair :
Le CSS est défini par des standards officiels afin que :
Le navigateur joue un rôle central :
Il existe plusieurs façons d’appliquer du CSS à une page HTML. Toutes fonctionnent techniquement, mais elles n’ont pas le même intérêt pédagogique ni professionnel.
Le style en ligne consiste à écrire le CSS directement dans la balise HTML, via l’attribut style.
<p style="color: red;">Texte rouge</p>
Fonctionnement :
style
Limites majeures :
Le style en ligne doit être considéré comme exceptionnel, jamais comme une méthode standard.
Objectif pédagogique
Comprendre concrètement la séparation des responsabilités entre HTML et CSS.
Consignes
On vous fournit le fichier HTML suivant :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Profil</title>
</head>
<body>
<h1 style="color: black;">Profil utilisateur</h1>
<p style="color: gray; font-size: 16px;">Développeur web junior</p>
</body>
</html>
style.css
Livrables attendus
index.htmlstyle.cssCorrection
index.html :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Profil</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Profil utilisateur</h1>
<p>Développeur web junior</p>
</body>
</html>
style.css :
h1 {
color: black;
}
p {
color: gray;
font-size: 16px;
}
Explications
Le style interne consiste à écrire le CSS dans une balise <style> située dans le <head> du document.
<style>
p {
color: blue;
}
</style>
Avantages :
Limites :
Objectif pédagogique
Comprendre pourquoi la feuille externe est la méthode de référence.
Consignes
On vous fournit ce fichier HTML :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Contact</title>
<style>
h1 {
color: darkblue;
}
p {
color: #333;
}
</style>
</head>
<body>
<h1>Contact</h1>
<p>contact@email.com</p>
</body>
</html>
<style>
style.css
Correction
index.html :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Contact</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Contact</h1>
<p>contact@email.com</p>
</body>
</html>
style.css :
h1 {
color: darkblue;
}
p {
color: #333;
}
Explications
La feuille de style externe est la méthode recommandée dans tous les projets professionnels.
Principe :
Comprendre comment le navigateur traite le CSS permet d’éviter de nombreuses erreurs.
Le navigateur suit un ordre précis :
Chaque règle est analysée :
Le CSS est lu de haut en bas.
p {
color: blue;
}
p {
color: red;
}
Résultat : le texte est rouge, car la dernière règle l’emporte.
Les navigateurs modernes intègrent des outils de développement permettant :
Les DevTools indiquent :
Les modifications effectuées via les DevTools :
Consignes
Objectif
Le sélecteur indique à quels éléments la règle s’applique.
p {
color: black;
}
Ici, tous les paragraphes sont concernés.
Chaque règle contient :
color: black;
p {
color: gray;
font-size: 14px;
}
Erreurs fréquentes :
Consignes
Corrigez le CSS suivant pour qu’il soit valide :
p {
color gray
font-size 16px
}
Correction
p {
color: gray;
font-size: 16px;
}
Explications
/* Ceci est un commentaire */
Les commentaires permettent :
Ils n’ont aucun impact sur le rendu visuel.
Dans la partie précédente, vous avez appris à écrire des règles CSS et à comprendre comment le navigateur les applique.
Une question essentielle se pose maintenant :
Comment le navigateur sait-il quels éléments HTML doivent recevoir quelle règle CSS ?
La réponse repose sur trois notions fondamentales :
Ces notions expliquent la majorité des comportements du CSS. Lorsqu’un style “ne s’applique pas” ou “est écrasé”, la cause se trouve presque toujours ici.
Les sélecteurs permettent de désigner les éléments HTML ciblés par une règle CSS.
Les sélecteurs simples sont les plus accessibles et constituent la base de tout le reste.
Le sélecteur par nom de balise cible tous les éléments d’un même type HTML, sans distinction.
Exemple :
p {
color: black;
}
Cette règle signifie :
<p>
color à chacun d’euxCaractéristiques importantes :
Ce type de sélecteur est souvent utilisé pour :
Limite majeure :
Objectif pédagogique
Observer qu’un sélecteur par balise s’applique à tous les éléments concernés, sans exception.
Consignes (HTML à produire par les élèves)
<body>, ajoutez :
Consignes CSS
Correction
HTML :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Sélecteur par balise</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<p>Introduction</p>
<p>Contenu principal</p>
<p>Conclusion</p>
</body>
</html>
CSS :
p {
color: blue;
}
Explications
Le sélecteur de classe permet de cibler un ou plusieurs éléments spécifiques, indépendamment de leur type HTML.
Une classe est définie avec l’attribut class en HTML.
Exemple HTML :
<p class="important">Texte important</p>
En CSS, une classe est précédée d’un point . :
.important {
color: red;
}
Caractéristiques fondamentales :
La classe est l’outil principal de la réutilisation en CSS.
Objectif pédagogique
Comprendre pourquoi la classe est plus adaptée qu’un sélecteur par balise pour cibler un élément précis.
Consignes (HTML à produire par les élèves)
Consignes CSS
Correction
HTML :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Classe CSS</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<p>Message standard</p>
<p class="important">Message important</p>
</body>
</html>
CSS :
.important {
color: red;
}
Explications
Le sélecteur d’identifiant (id) cible un élément unique dans une page HTML.
HTML :
<h1 id="main-title">Titre principal</h1>
CSS :
#main-title {
color: black;
}
Règle fondamentale :
Différences essentielles avec les classes :
Objectif pédagogique
Comprendre la notion d’unicité liée aux identifiants.
Consignes (HTML à produire par les élèves)
Consignes CSS
Correction
HTML :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Identifiant CSS</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1 id="page-title">Accueil</h1>
<p>Bienvenue sur le site</p>
</body>
</html>
CSS :
#page-title {
color: darkblue;
}
Explications
Les sélecteurs avancés permettent un ciblage basé sur :
Ils apportent une grande précision sans modifier le HTML.
Les sélecteurs d’attributs ciblent des éléments en fonction de leurs attributs HTML.
Exemple :
input[type="text"] {
border: 1px solid black;
}
Cette règle cible :
<input>
type vaut "text"
Consignes (HTML à produire par les élèves)
Consignes CSS
Correction
HTML :
<form>
<input type="text">
<input type="password">
</form>
CSS :
input[type="text"] {
background-color: #f0f0f0;
}
Explications
Les pseudo-classes décrivent un état particulier d’un élément.
Exemple :
a:hover {
color: red;
}
La règle s’applique uniquement :
Les pseudo-classes permettent :
Consignes (HTML à produire par les élèves)
Consignes CSS
Correction
HTML :
<a href="#">Voir plus</a>
CSS :
a:hover {
color: red;
}
Explications
Les pseudo-éléments ciblent une partie spécifique d’un élément.
Exemple :
p::first-letter {
font-size: 24px;
}
Ils permettent :
Consignes
Consignes CSS
Correction
HTML :
<p>Lorem ipsum dolor sit amet.</p>
CSS :
p::first-letter {
font-size: 24px;
}
Explications
Les sélecteurs peuvent être combinés pour cibler des éléments selon leur position dans la structure HTML.
div p {
color: blue;
}
Cible :
div
div > p {
color: blue;
}
Cible :
h2 + p {
color: blue;
}
Cible :
Consignes
Consignes CSS
Correction
HTML :
<h2>Section</h2>
<p>Introduction</p>
<p>Contenu</p>
CSS :
h2 + p {
color: blue;
}
Lorsque plusieurs règles s’appliquent à un même élément, le navigateur doit choisir.
C’est le principe de la cascade.
Certaines propriétés sont héritées automatiquement par les éléments enfants.
Exemple :
body {
color: black;
}
Les paragraphes héritent de cette couleur.
color, font-family
margin, border
Ordre de spécificité :
p {
color: blue;
}
.important {
color: red;
}
La classe l’emporte.
Objectif : structurer une “affiche” simple et la styliser avec balises, classes, id, et survol.
style.css (obligatoire).festival.headliner à un seul artiste (le plus important)#).h2 (sélecteur par balise) avec une couleur.#festival (couleur différente)..headliner (couleur différente + taille de texte plus grande).:hover) (couleur différente).Livrables
index.htmlstyle.cssindex.html
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Mini Line-up</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1 id="festival">Festival Mirage</h1>
<p>Une soirée unique, entre synthés et guitares, au bord de l’eau.</p>
<h2>Artistes</h2>
<ul>
<li class="headliner">Nova Atlas</li>
<li>Blue Tape</li>
<li>Signal & Co</li>
<li>Weekend Static</li>
</ul>
<h2>Infos</h2>
<p>Date : samedi 22 juin</p>
<p>Lieu : Scène du Phare</p>
<a href="#">Billetterie</a>
</body>
</html>
style.css
/* Titres de section */
h2 {
color: #2b2b2b;
}
/* Titre principal (unique) */
#festival {
color: #0b3d91;
}
/* Artiste mis en avant (réutilisable si besoin) */
.headliner {
color: #b00020;
font-size: 20px;
}
/* Interaction utilisateur */
a:hover {
color: #b00020;
}
h2 par balise : pratique pour des styles globaux.#festival : id = unicité, parfait pour un titre principal unique..headliner : classe = réutilisable, idéale pour “marquer” un élément.a:hover : pseudo-classe d’état utilisateur, visible uniquement au survol.Objectif : utiliser sélecteurs d’attributs pour cibler des champs sans classe, + pseudo-classes d’état.
style.css.:hover), changez sa couleur de texte.body.Livrables
index.htmlstyle.cssindex.html
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Inscription à la mission</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Inscription à la mission</h1>
<form>
<input type="text" placeholder="Nom de code">
<input type="email" placeholder="Contact">
<input type="password" placeholder="Accès">
<button type="submit">Valider</button>
</form>
<p>Vos données restent confidentielles.</p>
</body>
</html>
style.css
body {
color: #333;
}
/* Sélecteurs d’attributs */
input[type="text"] {
background-color: #f0f0f0;
}
input[type="password"] {
border: 2px solid #333;
}
/* État utilisateur */
button:hover {
color: #b00020;
}
body { color: ... } : la propriété color est héritée par les éléments enfants (dont p).border n’est pas héritée : si vous mettiez border sur body, les inputs ne la recevraient pas automatiquement.button:hover : interaction simple et directement visible.Objectif : pratiquer descendant vs enfant direct + organisation HTML propre.
style.css.div avec la classe feed
.feed, créez deux articles (balise article)
article contient :
div avec la classe meta contenant un paragraphe (p) “Publié il y a …”p contenus dans .feed (couleur).h2 enfants directs des article (couleur)..meta p avec une autre couleur (descendant).Livrables
index.htmlstyle.cssindex.html
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Actus express</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Actus express</h1>
<div class="feed">
<article>
<h2>Un nouveau studio ouvre</h2>
<p>Un espace dédié à la création visuelle vient d’ouvrir en centre-ville.</p>
<div class="meta">
<p>Publié il y a 2 heures</p>
</div>
</article>
<article>
<h2>Exposition nocturne</h2>
<p>Une exposition éphémère propose un parcours lumineux en extérieur.</p>
<div class="meta">
<p>Publié il y a 1 jour</p>
</div>
</article>
</div>
</body>
</html>
style.css
/* Tous les paragraphes dans le flux */
.feed p {
color: #333;
}
/* Uniquement les titres h2 directement sous article */
article > h2 {
color: #0b3d91;
}
/* Métadonnées (plus discrètes) */
.meta p {
color: #666;
}
.feed p (descendant) cible tous les paragraphes dans .feed, y compris ceux de .meta.article > h2 (enfant direct) évite d’attraper des h2 qui seraient plus profondément imbriqués..meta p permet de redonner un style spécifique aux paragraphes de métadonnées.Objectif : utiliser le sélecteur de frères adjacents +.
style.css.h2 + p, stylisez uniquement le paragraphe placé immédiatement après le h2 (couleur ou taille).Livrables
index.htmlstyle.cssindex.html
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Bande-annonce</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Bande-annonce</h1>
<section>
<h2>Synopsis</h2>
<p>Un message arrive trop tard, et tout bascule en une nuit.</p>
<p>Entre fuite et révélations, le héros comprend que tout était déjà écrit.</p>
</section>
</body>
</html>
style.css
h2 + p {
color: #b00020;
font-size: 18px;
}
h2 + p cible uniquement le paragraphe immédiatement adjacent.h2 et p, le style ne s’appliquera plus.Objectif : appliquer ::first-letter et comprendre le ciblage partiel.
style.css.p::first-letter.body { color: ... } et observez que le paragraphe hérite de cette couleur.Livrables
index.htmlstyle.cssindex.html
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Capsule</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Capsule</h1>
<p>
Dans la ville, les enseignes s’éteignent une à une. Un dernier bruit de pas résonne,
puis le silence reprend sa place.
</p>
</body>
</html>
style.css
body {
color: #333;
}
p::first-letter {
font-size: 28px;
}
p::first-letter cible une partie de l’élément, sans toucher au HTML.color définie sur body est héritée par le paragraphe (héritage).Objectif : provoquer volontairement des conflits et apprendre à les résoudre proprement.
style.css.important
unique
important
p { color: ... }
.important { color: ... }
#unique { color: ... }
Livrables
index.htmlstyle.cssindex.html
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Conflit de styles</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Conflit de styles</h1>
<p>Message normal</p>
<p class="important">Message important</p>
<p id="unique" class="important">Message unique</p>
</body>
</html>
style.css
p {
color: #333;
}
.important {
color: #b00020;
}
#unique {
color: #0b3d91;
}
p (balise) est moins spécifique que .important (classe).important est moins spécifique que #unique (id)p → couleur #333p + .important → la classe l’emportep + .important + #unique → l’id l’emporteLe texte est l’élément le plus présent dans une interface web.
Avant même les images, les animations ou les effets visuels, c’est le texte qui transmet l’information, guide l’utilisateur et structure la lecture.
La typographie joue donc un rôle central dans :
Cette partie vise à comprendre comment le CSS permet de contrôler précisément l’apparence du texte, à la fois d’un point de vue esthétique et fonctionnel.
La typographie ne se limite pas au choix d’une police.
Elle participe activement à l’expérience utilisateur et à la clarté d’une interface.
La lisibilité désigne la facilité avec laquelle un texte peut être lu et compris.
Elle dépend de plusieurs paramètres combinés :
La hiérarchie visuelle permet à l’utilisateur de comprendre immédiatement :
En CSS, cette hiérarchie est construite principalement grâce à :
font-sizefont-weightline-heightLe choix typographique influence fortement la perception d’un site :
Même avec un HTML identique, deux choix de polices différents peuvent produire des expériences très différentes.
Le CSS permet :
Le CSS propose plusieurs propriétés permettant de contrôler l’apparence des caractères.
La propriété font-family définit la police utilisée pour le texte.
body {
font-family: Arial, Helvetica, sans-serif;
}
Fonctionnement :
Familles génériques courantes :
serifsans-serifmonospaceBonne pratique :
Consignes (HTML à produire par les élèves)
style.css.Consignes CSS
body.Correction
HTML :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Typographie</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Manifeste</h1>
<p>Le texte structure l’interface.</p>
<p>La typographie guide la lecture.</p>
</body>
</html>
CSS :
body {
font-family: "Helvetica Neue", Arial, sans-serif;
}
Explications
La taille du texte est contrôlée par la propriété font-size.
p {
font-size: 16px;
}
La taille du texte :
Consignes (HTML à produire par les élèves)
Consignes CSS
Correction
CSS :
h1 {
font-size: 32px;
}
h2 {
font-size: 24px;
}
p {
font-size: 16px;
}
Explications
Deux propriétés principales :
font-weight : épaisseur du textefont-style : style du textestrong {
font-weight: bold;
}
em {
font-style: italic;
}
La mise en forme concerne la façon dont le texte est disposé et présenté dans l’espace.
La propriété text-align permet de contrôler l’alignement horizontal du texte.
Valeurs courantes :
leftcenterrightjustifyConsignes (HTML à produire par les élèves)
Consignes CSS
Correction
HTML :
<div class="card">
<h2>Carte éditoriale</h2>
<p>Ce texte est centré à l’intérieur de son conteneur.</p>
</div>
CSS :
.card {
width: 300px;
text-align: center;
}
Explications
text-align agit sur le contenu textuelDeux propriétés essentielles :
text-decorationtext-transformAjouter une décoration visuelle au texte :
souligner
barrer
ligne au-dessus
enlever le soulignement des liens
personnaliser le style du soulignement
Changer automatiquement la casse du texte :
tout en majuscules
tout en minuscules
première lettre en majuscule
capitaliser les mots
Consignes (HTML à produire par les élèves)
Consignes CSS
Correction
CSS :
a {
text-decoration: none;
}
p {
text-transform: uppercase;
}
Explications
text-decoration contrôle les éléments décoratifstext-transform agit sur l’affichage, pas sur le contenu HTMLLes espacements influencent fortement la lisibilité.
Propriétés clés :
line-height espace entre les lignesletter-spacing -> espace entre les lettresword-spacing -> espace entre les motsConsignes (HTML à produire par les élèves)
Consignes CSS
line-height pour améliorez la lisibilité du texte.letter-spacing et word-spacing pour vous entrainer avec les propriétés.Correction
CSS :
p {
line-height: 1.6;
letter-spacing: 0.5px;
}
Explications
line-height améliore immédiatement le confort de lectureletter-spacing doit rester subtilLes effets doivent toujours rester au service du contenu.
La propriété text-shadow permet d’ajouter une ombre.
h1 {
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
Consignes (HTML à produire par les élèves)
Consignes CSS
Correction
CSS :
h1 {
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
Explications
Une ombre :
En CSS, une unité de mesure indique comment le navigateur doit interpréter une valeur : taille d’un texte, marge, largeur, etc.
Une unité absolue correspond à une valeur que le navigateur traite comme stable et indépendante du contexte.
px (pixel) est l’unité absolue la plus utilisée en CSS.Exemple :
p {
font-size: 16px;
}
Ici, le texte est défini à 16 px, quel que soit :
Avantage : prévisible, facile pour débuter
Limite : moins flexible si on veut une interface qui s’adapte bien aux contextes (changements de taille de base, cohérence globale).
Une unité relative est calculée à partir de quelque chose : taille du parent, taille de base du document, taille du viewport, etc.
Exemples d’unités relatives :
% → par rapport au parent. Ex: width:50%; l’élément prend 50% de la largeur de son parent.
em → par rapport à la taille du texte locale: Dimension basée sur la taille de police de l’élément courant.
rem → par rapport au texte racine. Dimension basée sur la taille du texte du document (html).
vw → par rapport à la largeur de l’écran. Dimension basée sur la largeur de l’écran. Ex: 1vw = 1% largeur fenêtre.
vh → par rapport à la hauteur de l’écran. Dimension basée sur la hauteur de l’écran. Ex: 1vh = 1% hauteur fenêtre
Le principe : la valeur s’adapte selon le contexte.
Cette sous-partie est centrale : c’est ici que vous comprenez ce que “relatif” veut dire en pratique.
px : taille fixe (simple, local)px est souvent utilisé pour :
h1 {
font-size: 32px;
}
Vous obtenez une taille stable.
Mais si vous souhaitez augmenter la taille globale de tout le site, il faudra modifier beaucoup de règles.
% : relatif à une valeur du parent% correspond à une valeur proportionnelle à celle du parent.
Exemple :
.container {
font-size: 20px;
}
.container p {
font-size: 80%;
}
Ici :
.container définit une base à 20pxLe point clé : % dépend du parent.
em : relatif à la taille de police du contexteem est basé sur la taille de police utilisée comme référence dans le contexte courant (souvent le parent).
Exemple :
.container {
font-size: 20px;
}
.container p {
font-size: 1em;
}
Ici :
1em vaut 20pxAutre exemple :
.container p {
font-size: 0.8em;
}
Le paragraphe fait alors 16px.
À quoi sert em ?
Piège classique
.container {
font-size: 20px;
}
.container p {
font-size: 1em;
}
.container p span {
font-size: 1.2em;
}
Les tailles peuvent s’accumuler si l’imbrication devient profonde.
rem : relatif à la racine du documentrem signifie root em.
Il est toujours basé sur la taille définie sur html.
Exemple :
html {
font-size: 16px;
}
p {
font-size: 1rem;
}
Ici :
1rem vaut toujours 16pxp {
font-size: 1.25rem;
}
Donne 20px.
À quoi sert rem ?
em
Règle pratique
rem pour les tailles globalesem pour des ajustements locauxConsignes (HTML à produire par les élèves)
style.css.container
span autour d’un mot dans le paragrapheConsignes CSS
html..container.em.rem.span en em.html puis .container pour observer les différences.Correction
HTML :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>em vs rem</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h2>Titre du bloc</h2>
<p>Ce paragraphe contient un mot <span>accentué</span> pour comparer les unités.</p>
</div>
</body>
</html>
CSS :
html {
font-size: 16px;
}
.container {
font-size: 20px;
}
.container p {
font-size: 1em;
}
.container p span {
font-size: 1.2em;
}
.container h2 {
font-size: 1.5rem;
}
Explications
p dépend de .container → 20px span dépend du p → 24px h2 dépend de html → 24px .container change p et span, pas h2
html change h2, pas p ni span
Le viewport correspond à la zone visible du navigateur.
vw : 1% de la largeur du viewportvh : 1% de la hauteur du viewportExemple :
h1 {
font-size: 5vw;
}
La taille varie selon la largeur de l’écran.
À quoi ça sert ?
Point d’attention Une utilisation excessive peut nuire à la lisibilité.
Consignes (HTML à produire par les élèves)
style.css.Consignes CSS
vw.Correction
CSS :
h1 {
font-size: 6vw;
}
p {
font-size: 16px;
}
Explications
Les polices web permettent d’utiliser une police non installée sur l’appareil de l’utilisateur.
Le navigateur télécharge la police au chargement de la page.
@font-face {
font-family: "MaPolice";
src: url("mapolice.woff2");
}
body {
font-family: "MaPolice", sans-serif;
}
Bonnes pratiques :
Contexte
Vous devez créer une carte contenant un message confidentiel.
style.css.div avec la classe secret-card
important sur un seul mot du message principal..secret-card :
.important.HTML :
<div class="secret-card">
<h2>Message confidentiel</h2>
<p>Le rendez-vous aura lieu à <span class="important">minuit</span>.</p>
<p>— Source inconnue</p>
</div>
CSS :
body {
font-family: Arial, sans-serif;
}
.secret-card {
width: 320px;
text-align: center;
line-height: 1.6;
}
.important {
font-weight: bold;
color: #b00020;
}
.secret-card:hover {
opacity: 0.9;
}
:hover ajoute une interaction simple et maîtriséeContexte
Vous créez une fiche de présentation pour un personnage fictif.
div avec la classe profile
alias sur le nom du personnage..alias) :
rem
HTML :
<div class="profile">
<h2 class="alias">Le Veilleur</h2>
<p>Personnage discret, rarement aperçu, toujours présent.</p>
<p>Statut : actif</p>
</div>
CSS :
body {
font-family: Arial, sans-serif;
}
.profile {
width: 300px;
line-height: 1.6;
}
.alias {
font-size: 1.6rem;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3);
}
rem garantit une taille cohérente à l’échelle du documentContexte
Vous devez mettre en forme un court manifeste idéologique.
emphasis sur une phrase entière du manifeste..emphasis différemment (taille ou style).em pour un ajustement local.CSS :
h1 {
font-size: 2rem;
}
p {
font-size: 1rem;
line-height: 1.6;
}
.emphasis {
font-size: 1.2em;
font-style: italic;
}
em dépend du contexte du paragrapheContexte
Vous concevez un message d’alerte pour une interface futuriste.
div avec la classe warning
critical sur un mot clé..warning une largeur fixe.text-transform sur le h2..critical.CSS :
.warning {
width: 320px;
text-align: center;
}
h2 {
text-transform: uppercase;
}
.critical {
font-weight: bold;
font-size: 1.2em;
}
.warning:hover {
opacity: 0.85;
}
text-transform agit uniquement sur l’affichageContexte
Vous créez une affiche textuelle annonçant une prophétie.
vision sur le h1.CSS :
h1.vision {
font-size: 6vw;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
p {
font-size: 1rem;
line-height: 1.6;
}
vw rend le titre adaptatif à la taille de l’écranLa couleur est l’un des leviers les plus puissants du CSS.
Elle influence immédiatement :
Contrairement à ce que l’on pourrait penser, la couleur en CSS n’est pas qu’un choix esthétique :
c’est un outil fonctionnel, soumis à des règles précises.
Avant d’appliquer des couleurs, il est essentiel de comprendre comment le navigateur les interprète.
Le CSS propose plusieurs systèmes de notation, qui ne servent pas tous aux mêmes usages.
Le CSS fournit une liste de noms de couleurs prédéfinis, comme :
p {
color: red;
}
Exemples courants :
blackwhiteredbluegreengrayÀ quoi ça sert
Limites
👉 En pratique, ces couleurs sont surtout utiles pour apprendre ou tester, pas pour construire une identité graphique.
La notation hexadécimale est l’une des plus utilisées sur le web.
Exemple :
p {
color: #ff0000;
}
Fonctionnement :
#RRGGBB00 à FF
Exemples :
#000000 → noir#ffffff → blanc#ff0000 → rougeVersion raccourcie possible :
p {
color: #f00;
}
À quoi ça sert
Consignes HTML
style.css.Consignes CSS
Correction (exemple)
p:first-of-type {
color: #ff0000;
}
p:nth-of-type(2) {
color: #00ff00;
}
p:nth-of-type(3) {
color: #00f;
}
Explications
Le modèle RGB représente les couleurs par leurs composantes :
Exemple :
p {
color: rgb(255, 0, 0);
}
Chaque valeur va de 0 à 255.
RGBA ajoute un canal alpha, qui gère la transparence :
p {
color: rgba(255, 0, 0, 0.5);
}
Le dernier paramètre :
0 (transparent)1 (opaque)À quoi ça sert
Consignes
rgba.Correction
p {
color: rgba(0, 0, 0, 0.6);
}
Explications
opacity (vue plus tard)HSL signifie :
Exemple :
p {
color: hsl(0, 100%, 50%);
}
HSLA ajoute la transparence :
p {
color: hsla(0, 100%, 50%, 0.7);
}
À quoi ça sert
👉 HSL est souvent plus lisible mentalement que RGB.
La propriété principale est color.
p {
color: #333333;
}
Elle s’applique à :
Cela signifie que :
color sur body impacte tout le texteUne couleur n’est jamais isolée :
elle est toujours perçue par rapport à son arrière-plan.
Bonnes pratiques :
Consignes
Correction
div {
background-color: #f5f5f5;
}
p {
color: #333333;
}
Explications
La propriété background-color définit la couleur derrière un élément.
div {
background-color: #eeeeee;
}
Elle s’applique :
Une image peut être utilisée comme arrière-plan :
div {
background-image: url("image.jpg");
}
À quoi ça sert
Consignes
Correction
.card {
background-image: url("background.jpg");
color: white;
}
Explications
Par défaut, une image d’arrière-plan se répète.
div {
background-repeat: no-repeat;
}
Valeurs courantes :
repeatno-repeatrepeat-xrepeat-yPosition :
div {
background-position: center;
}
Dimensionnement :
div {
background-size: cover;
}
cover remplit l’espacecontain conserve l’image entièreConsignes
Correction
div {
background-image: url("background.jpg");
background-repeat: no-repeat;
background-position: center;
background-size: cover;
}
Le CSS permet plusieurs arrière-plans sur un même élément :
div {
background-image:
linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)),
url("image.jpg");
}
Les arrière-plans sont empilés du haut vers le bas.
div {
background: linear-gradient(to right, #000, #fff);
}
div {
background: radial-gradient(circle, #fff, #000);
}
opacity affecte tout l’élément :
div {
opacity: 0.5;
}
RGBA / HSLA affecte uniquement la couleur :
div {
background-color: rgba(0,0,0,0.5);
}
👉 C’est une différence fondamentale à comprendre.
Consignes
opacity.Correction
.box-opacity {
background-color: black;
opacity: 0.5;
}
.box-rgba {
background-color: rgba(0, 0, 0, 0.5);
}
Explications
opacity rend aussi le texte transparentLe Box Model est un concept central en CSS. Chaque élément HTML est représenté comme une boîte rectangulaire avec contenu, padding, bordure et marge.
Maîtriser le Box Model est indispensable pour :
Chaque boîte CSS est composée de quatre zones imbriquées :
+---------------------------+
| margin |
| +-------------------+ |
| | border | |
| | +-------------+ | |
| | | padding | | |
| | | +-------+ | | |
| | | |content| | | |
| | | +-------+ | | |
| | +-------------+ | |
| +-------------------+ |
+---------------------------+
Chaque zone a son rôle :
La zone de contenu correspond à l’espace où s’affiche le texte ou les éléments enfants.
Exemple :
div.content-box {
width: 300px; /* largeur du contenu */
height: 150px; /* hauteur du contenu */
background-color: #f0f0f0; /* couleur de fond pour visualiser le content */
}
💡 Explications :
box-sizing: content-box. index.html. <body>, ajoutez une div avec la classe content-box. .content-box. Le padding est l’espace entre le contenu et la bordure.
div.padding-box {
padding: 20px; /* même padding pour tous les côtés */
background-color: #d0e6f0;
border: 2px solid #333;
}
💡 Explications :
padding-top: 10px;
padding-right: 15px;
padding-bottom: 20px;
padding-left: 25px;
padding: 10px 15px 20px 25px; /* top right bottom left */
padding: 10px 20px; /* top/bottom, right/left */
padding: 10px; /* tous les côtés identiques */
Exemple concret :
div.example {
width: 200px;
height: 100px;
padding: 10px 20px; /* 10px haut et bas, 20px droite et gauche */
border: 3px solid black;
background-color: #f7e6f0;
}
💡 Pièges fréquents :
width fixe.div avec la classe padding-box. <!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Exercice Padding</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="padding-box">
<h2>Mon titre</h2>
<p>Voici un paragraphe pour observer l'effet du padding sur la distance entre le texte et la bordure.</p>
</div>
</body>
</html>
.padding-box {
/* Padding individuel pour chaque côté */
padding-top: 20px;
padding-right: 40px;
padding-bottom: 30px;
padding-left: 10px;
/* Ou en shorthand (alternative) */
/* padding: 20px 40px 30px 10px; */
/* Bordure et couleur de fond */
border: 2px solid #333;
background-color: #f0f0f0;
/* Pour mieux visualiser le contenu */
max-width: 400px;
margin: 20px auto;
}
La bordure entoure le padding et délimite visuellement l’élément.
div.border-box {
padding: 15px;
border: 4px dashed #ff6600;
background-color: #fff3e6;
}
💡 Explications :
border-width, border-style, border-color. border-top: 3px solid red;
border-right: 5px dotted blue;
border-bottom: 2px dashed green;
border-left: 4px solid black;
border: 2px solid black;
div avec la classe border-box.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Exercice Border</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="border-box">
<h2>Mon titre</h2>
<p>Voici un paragraphe pour observer l'effet des bordures sur la taille et l'apparence de la div.</p>
</div>
</body
.border-box {
/* Padding pour que le texte ne touche pas la bordure */
padding: 15px;
/* Bordure : différentes propriétés */
border-width: 4px; /* épaisseur de la bordure */
border-style: dashed; /* style : solid, dashed, dotted, double, groove... */
border-color: #ff6600; /* couleur de la bordure */
/* Option : couleur de fond pour mieux voir l'effet */
background-color: #ffeedd;
/* Taille et centrage pour visualisation */
width: 300px;
margin: 20px auto;
box-sizing: border-box; /* inclut la bordure et le padding dans la largeur totale */
}
La marge crée un espace autour de l’élément, séparant les boîtes entre elles.
div.margin-box {
padding: 10px;
border: 2px solid #333;
margin: 20px; /* espace externe */
background-color: #e6f7d0;
}
💡 Explications :
margin-top: 10px;
margin-right: 15px;
margin-bottom: 20px;
margin-left: 25px;
margin: 10px 15px 20px 25px; /* top right bottom left */
margin: 10px 20px; /* top/bottom, right/left */
margin: 10px; /* tous les côtés identiques */
div successives avec la classe margin-box.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Exercice Margin</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="margin-box">
<h2>Boîte 1</h2>
<p>Voici le contenu de la première boîte pour tester les marges.</p>
</div>
<div class="margin-box">
<h2>Boîte 2</h2>
<p>Voici le contenu de la deuxième boîte pour observer l’espace entre les deux.</p>
</div>
</body>
</html>
.margin-box {
/* Padding interne pour que le texte ne touche pas les bords */
padding: 15px;
/* Bordure et fond pour visualiser la boîte */
border: 2px solid #444;
background-color: #ddeeff;
/* Largeur et centrage */
width: 300px;
margin-left: auto;
margin-right: auto;
}
/* Marges différentes pour chaque boîte */
.margin-box:nth-child(1) {
margin-top: 20px;
margin-bottom: 40px;
}
.margin-box:nth-child(2) {
margin-top: 10px;
margin-bottom: 30px;
}
Définissent la taille du contenu uniquement.
div.dimension-box {
width: 200px;
height: 100px;
padding: 20px;
border: 5px solid #333;
background-color: #f2e6ff;
}
💡 Explications :
div avec les classes box1 et box2. box2 seulement, utilisez box-sizing: border-box. Permettent de limiter les dimensions :
div.limit-box {
width: 50%;
max-width: 600px;
min-width: 200px;
}
div contenant plusieurs paragraphes.min-width et max-width. Par défaut, width et height ne prennent pas en compte le padding et la bordure (content-box).
div.content-box {
box-sizing: content-box;
}
Avec border-box, le padding et la border sont inclus dans la largeur totale :
div.border-box {
box-sizing: border-box;
}
div avec texte identique. content-box, l’autre border-box.div.shadow-box {
padding: 20px;
border: 2px solid #333;
box-shadow: 5px 5px 10px rgba(0,0,0,0.3);
background-color: #fffacd;
}
💡 Explications :
div avec la classe shadow-box. box-shadow.Le Box Model est essentiel pour créer des interfaces CSS propres et maîtrisées :
Maîtriser ces notions permet de prévoir la taille réelle d’un élément, de gérer les espacements, et de produire des interfaces lisibles et harmonieuses.
Objectif : produire une carte “dossier secret” structurée et sémantiquement simple.
Créez un fichier index.html complet comprenant :
<!DOCTYPE html><html lang="fr"><head> avec :<meta charset="UTF-8"><title> cohérentstyle.css
<body>Dans le <body>, créez un seul conteneur principal : une div avec la classe dossier.
À l’intérieur de .dossier, ajoutez dans cet ordre :
h1 contenant un nom de code (ex : “NOM DE CODE : …”) et donnez-lui l’id code-name (cet id doit être unique).p d’introduction (2 phrases minimum).h2 avec le texte “Détails”.ul contenant exactement 3 éléments li (ex : Niveau, Zone, Statut).a en bas avec le texte “Déclassifier” et un href="#".Dans la liste, ajoutez la classe flag à un seul li (celui qui doit ressortir visuellement).
Objectif : hiérarchie typographique + box model + dégradé + interaction.
Sur body :
Sur .dossier :
px
margin
padding
border
linear-gradient() comportant au moins deux couleursCréez une hiérarchie :
#code-name doit être plus grand que h2 et les paragraphes (tailles en rem)h2 doit être clairement identifié (taille, marge verticale)Mettez en valeur .flag :
Style du lien :
:hover, changez la couleur du lienAjoutez au moins un commentaire CSS pour structurer le fichier.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Dossier d’espion</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="dossier">
<h1 id="code-name">NOM DE CODE : ORION</h1>
<p>Document interne. Diffusion restreinte. Toute reproduction est interdite.</p>
<h2>Détails</h2>
<ul>
<li>Niveau : 3</li>
<li class="flag">Statut : prioritaire</li>
<li>Zone : secteur nord</li>
</ul>
<a href="#">Déclassifier</a>
</div>
</body>
</html>
/* Base globale : typographie et héritage */
body {
font-family: Arial, sans-serif;
color: #222;
}
/* Carte principale : box model */
.dossier {
width: 360px;
margin: 24px;
padding: 18px;
border: 2px solid #222;
background: linear-gradient(to bottom, #f5f5f5, #e6e6e6);
line-height: 1.6;
}
/* Hiérarchie typographique */
#code-name {
font-size: 1.6rem;
text-transform: uppercase;
margin: 0 0 10px 0;
}
h2 {
font-size: 1.2rem;
margin: 14px 0 8px 0;
}
.flag {
color: #b00020;
font-weight: bold;
}
/* Lien */
a {
text-decoration: none;
color: #0b3d91;
}
a:hover {
color: #b00020;
}
rem assurent une cohérence d’échelle.:hover introduit une interaction simple.margin (extérieur) et padding (intérieur).style.css externe.div class grimoire
h1 (titre du sort)p long (2–3 lignes minimum)p court “Ingrédients : …”incantation au paragraphe long.body..grimoire doit avoir :
px
background-color en HSL ou HSLA (obligatoire)h1 :
rem
text-shadow discret.incantation::first-letter :
em)rem
em
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Grimoire</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="grimoire">
<h1>Sortilège : Brume Légère</h1>
<p class="incantation">
Lorsque la nuit tombe, prononcez ces mots à voix basse. La brume couvre les traces, et le silence protège votre passage.
</p>
<p>Ingrédients : feuille sèche, eau, souffle.</p>
</div>
</body>
</html>
body {
font-family: Arial, sans-serif;
color: #1f1f1f;
}
.grimoire {
width: 380px;
margin: 24px;
padding: 18px;
border: 2px solid #1f1f1f;
background-color: hsl(45, 40%, 92%);
line-height: 1.6;
}
h1 {
font-size: 1.7rem;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.25);
margin: 0 0 12px 0;
}
.incantation {
font-size: 1rem;
margin: 0 0 10px 0;
}
.incantation::first-letter {
font-size: 2em;
font-weight: bold;
}
p {
margin: 0 0 10px 0;
}
::first-letter : permet de styliser une partie d’un texte sans modifier le HTML (effet “manuscrit”).rem sert aux tailles globales (titre)em sert à des ajustements locaux (la première lettre par rapport au paragraphe)opacity (rend tout transparent) et une couleur HSLA (ne rend transparent que la couleur)But : créer un ticket typographique, avec contraste, hover, unités viewport.
div class ticket
h1 class game-title
h2 “Score”p contenant un nombrea “Rejouer”span class bonus autour d’un mot dans le paragraphe (ex : “bonus”).h1 en unité vw (titre qui s’adapte à l’écran)..ticket : width fixe + padding + border + background-color + margin..bonus en em (plus grand que le texte autour).a:hover (couleur).<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Arcade</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="ticket">
<h1 class="game-title">NEON RUN</h1>
<h2>Score</h2>
<p>Vous avez obtenu 12800 points <span class="bonus">bonus</span>.</p>
<a href="#">Rejouer</a>
</div>
</body>
</html>
body {
font-family: Arial, sans-serif;
color: #111;
}
.ticket {
width: 360px;
margin: 24px;
padding: 18px;
border: 2px dashed #111;
background-color: rgba(240, 240, 240, 1);
line-height: 1.6;
}
.game-title {
font-size: 6vw;
margin: 0 0 8px 0;
color: #0b3d91;
}
h2 {
font-size: 1.2rem;
margin: 10px 0 6px 0;
}
p {
margin: 0 0 12px 0;
}
.bonus {
font-size: 1.2em;
font-weight: bold;
color: #b00020;
}
a {
text-decoration: none;
color: #0b3d91;
}
a:hover {
color: #b00020;
}
.bonus : le bonus grossit proportionnellement au texte du paragraphe.vw trop grand → titre énorme sur grands écransfont-size du paragraphe en px partout → perte de cohérence globaleBut : manipuler sélecteurs d’attributs + couleurs + box model.
div class shelf contenant 3 “étiquettes” :
div class label
.label contient un h2 + un p
.label, ajoutez un attribut data-type avec une valeur différente :
data-type="heal", data-type="poison", data-type="mana"
.shelf : width fixe + padding + background-color..label : border + margin-bottom + padding.text-transform sur les titres.:hover sur .label (léger changement de fond ou d’opacité).<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Potions</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="shelf">
<div class="label" data-type="heal">
<h2>Potion de soin</h2>
<p>Usage : régénération lente.</p>
</div>
<div class="label" data-type="poison">
<h2>Potion toxique</h2>
<p>Usage : à manipuler avec prudence.</p>
</div>
<div class="label" data-type="mana">
<h2>Potion de mana</h2>
<p>Usage : concentration accrue.</p>
</div>
</div>
</body>
</html>
body {
font-family: Arial, sans-serif;
color: #222;
}
.shelf {
width: 420px;
margin: 24px;
padding: 18px;
background-color: #f5f5f5;
border: 2px solid #222;
}
.label {
padding: 14px;
border: 2px solid #222;
margin-bottom: 12px;
line-height: 1.6;
}
.label:hover {
opacity: 0.92;
}
.label h2 {
text-transform: uppercase;
margin: 0 0 6px 0;
font-size: 1.2rem;
}
/* Couleurs par attribut */
.label[data-type="heal"] {
background-color: rgba(0, 160, 80, 0.12);
}
.label[data-type="poison"] {
background-color: rgba(176, 0, 32, 0.12);
}
.label[data-type="mana"] {
background-color: rgba(11, 61, 145, 0.12);
}
data-type sans rajouter des classes “heal/poison/mana”..label : renforce l’interactivité sans introduire de concepts avancés..label (cela affecterait aussi le texte)But : cumuler image de fond + réglages background + contraste + overlay sans opacity globale.
div class poster
.poster : un h1, un p (pitch), un lien “Réserver”.poster : width fixe + padding + border + margin.background-image avec 2 couches :
linear-gradient semi-transparenturl(...) (image au choix)cover.h1 : letter-spacing léger + ombre portée.text-decoration: none et :hover couleur.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Nuit Blanche</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="poster">
<h1>Nuit Blanche</h1>
<p>Une ville s’endort. Un message s’allume. Tout change en une heure.</p>
<a href="#">Réserver</a>
</div>
</body>
</html>
body {
font-family: Arial, sans-serif;
}
.poster {
width: 440px;
margin: 24px;
padding: 18px;
border: 2px solid #111;
color: #ffffff;
line-height: 1.6;
background-image:
linear-gradient(rgba(0,0,0,0.55), rgba(0,0,0,0.55)),
url("background.jpg");
background-repeat: no-repeat;
background-position: center;
background-size: cover;
}
.poster h1 {
margin: 0 0 8px 0;
font-size: 2rem;
letter-spacing: 1px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.35);
}
.poster p {
margin: 0 0 12px 0;
font-size: 1rem;
}
.poster a {
text-decoration: none;
color: #ffffff;
}
.poster a:hover {
color: #ffcc00;
}
opacity : opacity aurait rendu le texte transparent aussi, ce qu’on évite.no-repeat et voir l’image se répétertext-decoration: none sur le lienstyle.css.div class lab..lab, créez 2 blocs :
div class case case-opacity
div class case case-rgba
.case, mettez :
h2
p (même contenu dans les deux, pour comparer)a (lien “Détails”).lab : width fixe + padding + border + background-color + margin..case : padding + border + margin-bottom..case-opacity, utilisez opacity: .....case-rgba, utilisez un fond rgba(...) (pas d’opacité globale).a:hover + suppression du soulignement.rem pour les titres.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Laboratoire</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="lab">
<div class="case case-opacity">
<h2>Version opacity</h2>
<p>Comparez la lisibilité : ici, tout l’élément devient transparent.</p>
<a href="#">Détails</a>
</div>
<div class="case case-rgba">
<h2>Version RGBA</h2>
<p>Comparez la lisibilité : ici, seul le fond est semi-transparent.</p>
<a href="#">Détails</a>
</div>
</div>
</body>
</html>
body {
font-family: Arial, sans-serif;
color: #111;
}
.lab {
width: 460px;
margin: 24px;
padding: 18px;
border: 2px solid #111;
background-color: #f5f5f5;
}
.case {
padding: 14px;
border: 2px solid #111;
margin-bottom: 12px;
line-height: 1.6;
}
.case h2 {
font-size: 1.3rem;
margin: 0 0 6px 0;
}
.case a {
text-decoration: none;
color: #0b3d91;
}
.case a:hover {
color: #b00020;
}
.case-opacity {
background-color: #0b3d91;
color: #ffffff;
opacity: 0.6;
}
.case-rgba {
background-color: rgba(11, 61, 145, 0.6);
color: #ffffff;
}
opacity s’applique à tous les enfantsCe module est l'un des piliers fondamentaux du CSS. Comprendre comment le navigateur organise les éléments sur une page, comment les positionner avec précision et comment contrôler leur superposition vous donnera la maîtrise totale de la mise en page web.
Sans cette compréhension, les comportements du navigateur semblent souvent mystérieux, voire contradictoires. Avec elle, vous serez capable de prévoir et de reproduire n'importe quelle mise en page.
Avant de parler de positionnement, de flottants ou de Flexbox, il est indispensable de comprendre ce que le navigateur fait par défaut lorsqu'il affiche une page HTML. Ce comportement par défaut s'appelle le flux normal du document, ou plus simplement le flux.
Le flux est la mécanique invisible qui gouverne chaque page web que vous avez jamais vue. Chaque développeur web, qu'il soit débutant ou expert, doit maîtriser ce concept avant toute chose. C'est la fondation sur laquelle repose tout le reste.
Lorsqu'un navigateur reçoit un fichier HTML, il le lit de haut en bas, dans l'ordre du code source, et place chaque élément les uns à la suite des autres. Ce mécanisme obéit à deux règles simples et immuables :
Considérez ce HTML très simple, sans aucune ligne de CSS :
<h1>Titre principal</h1>
<p>Un premier paragraphe de texte.</p>
<p>Un second paragraphe. Le mot <strong>important</strong> est en ligne.</p>
<div>Une division bloc.</div>
<span>Un span</span>
<span>Un autre span</span>
<span>Encore un span</span>
Le navigateur va placer h1, p et div empilés verticalement (éléments blocs), et les trois span côte à côte sur la même ligne (éléments en ligne). Le mot strong s'insère dans le texte sans créer de saut de ligne.
Point clé : le flux normal est gratuit et automatique. Il ne nécessite zéro CSS. C'est le comportement de base du navigateur, et c'est sur cette base que vous allez construire toutes vos mises en page.
Il est essentiel de retenir que le flux garantit un ordre de lecture identique à l'ordre du code HTML. Cet ordre est fondamental pour l'accessibilité (les lecteurs d'écran lisent le DOM dans cet ordre) et pour le référencement naturel.
Le flux normal a cinq conséquences directes sur la manière dont vous écrivez votre HTML et votre CSS. Comprendre ces conséquences vous évitera de nombreuses erreurs et frustrations.
Conséquence 1 : la largeur automatique des blocs.
Par défaut, un élément bloc occupe 100% de la largeur de son conteneur. Un div enfant d'un body de 1200px fera lui aussi 1200px, sans que vous ayez besoin de le préciser. Cette largeur est calculée automatiquement.
div {
/* Pas besoin d'écrire width: 100%; c'est déjà le cas par défaut */
background-color: lightblue;
padding: 20px;
}
/* Si on veut le réduire, on fixe une valeur explicite */
div.reduit {
width: 60%;
}
Conséquence 2 : la hauteur automatique.
Un élément bloc s'étend verticalement pour englober tout son contenu. Si vous avez un div contenant trois paragraphes, le div sera aussi haut que leur somme. La hauteur n'a pas besoin d'être fixée manuellement. Si vous la fixez et que le contenu dépasse, gérez cela avec overflow.
div.hauteur-fixe {
height: 100px;
overflow: hidden; /* Le contenu qui dépasse est masqué */
/* ou : overflow: scroll; pour ajouter une barre de défilement */
}
Conséquence 3 : la fusion des marges (margin collapsing). Lorsque deux éléments blocs adjacents ont chacun une marge verticale, ces marges ne s'additionnent pas — elles fusionnent : seule la plus grande est appliquée. C'est un comportement propre au flux normal et il ne concerne que les marges verticales. Les marges horizontales ne fusionnent jamais.
p.premier {
margin-bottom: 30px;
}
p.second {
margin-top: 20px;
}
/*
L'espace entre les deux paragraphes n'est PAS de 50px.
Il est de 30px : les marges ont fusionné, la plus grande gagne.
Pour le constater : DevTools (F12) > sélectionner p.premier > Box Model.
*/
Attention, piège classique : on s'attend à ce que les marges s'additionnent. Vérifiez toujours dans le Box Model des DevTools lorsqu'un espacement ne correspond pas à ce que vous attendez.
Conséquence 4 : les éléments en ligne ignorent width et height.
Si vous écrivez width: 200px sur un span, rien ne se passera. Les éléments en ligne prennent uniquement la place nécessaire à leur contenu. Les paddings et marges verticaux sont également sans effet sur le flux.
span {
width: 300px; /* Ignoré */
height: 100px; /* Ignoré */
padding-left: 8px; /* OK, les paddings horizontaux fonctionnent */
padding-right: 8px; /* OK */
}
Conséquence 5 : sortir du flux.
Les propriétés float, position: absolute et position: fixed retirent un élément du flux normal. Les autres éléments se comportent alors comme s'il n'existait plus, et peuvent se placer sous lui ou le recouvrir. C'est puissant, mais source de nombreux bugs si mal compris. Nous détaillerons tout cela dans les sections 6.4 et 6.6.
Objectif : créer une carte de visite simple en utilisant uniquement le flux normal, sans aucune propriété de positionnement. L'exercice vous amène à observer concrètement le comportement des éléments blocs et en ligne, et à constater la fusion des marges en direct dans les DevTools.
Consignes :
carte.html avec le HTML fourni ci-dessous.style.css lié. Propriétés autorisées : background-color, color, padding, margin, font-size, font-weight, border, max-width, font-family. Aucune propriété de positionnement.
margin-bottom: 30px sur h1 et margin-top: 20px sur .poste. Observez dans les DevTools que l'espace réel est de 30px, pas 50px.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
<title>Carte de visite</title>
</head>
<body>
<div class="carte">
<h1>Marie Dupont</h1>
<p class="poste">Développeuse Web Front-End</p>
<hr>
<p>
Email : <span class="info">marie@exemple.fr</span>
|
Tél : <span class="info">06 12 34 56 78</span>
</p>
<p class="bio">
Passionnée par le CSS et l'UX Design.
<strong>Disponible</strong> pour des missions freelance.
</p>
</div>
</body>
</html>
/* On retire les marges par défaut du navigateur */
body {
margin: 0;
padding: 40px;
background-color: #f0f4f8;
font-family: Arial, sans-serif;
}
/*
La carte est un élément bloc : elle occupe 100% de la largeur du body.
max-width limite cette largeur. margin: 0 auto centre horizontalement.
*/
.carte {
background-color: #ffffff;
border: 1px solid #cccccc;
border-top: 4px solid #2E6DA4;
padding: 30px 40px;
max-width: 520px;
margin: 0 auto;
}
/*
h1 est un élément bloc : il prend toute la ligne.
margin-bottom: 30px fusionnera avec margin-top: 20px de .poste.
Résultat visible dans les DevTools : 30px d'espace, pas 50px.
*/
h1 {
font-size: 28px;
color: #1A3A5C;
margin-top: 0;
margin-bottom: 30px;
}
/*
.poste est un <p> (bloc) : il passe à la ligne sous h1.
margin-top: 20px fusionne avec margin-bottom de h1.
Seule la plus grande valeur (30px) s'applique.
*/
.poste {
font-size: 16px;
color: #2E6DA4;
font-weight: bold;
margin-top: 20px;
margin-bottom: 0;
}
hr {
border: none;
border-top: 1px solid #cccccc;
margin: 20px 0;
}
/*
Les .info sont des <span> : éléments en ligne.
Ils restent dans la phrase. width et height n'auraient aucun effet ici.
*/
.info {
color: #C0392B;
font-weight: bold;
background-color: #FEF9F9;
padding: 2px 6px;
border-radius: 3px;
}
.bio {
font-size: 14px;
color: #555555;
line-height: 1.7;
margin-bottom: 0;
}
/* <strong> est en ligne : il s'insère dans le texte sans saut de ligne */
.bio strong {
color: #1A3A5C;
}
Quels éléments sont empilés ? h1, p.poste, hr, p, p.bio — tous des blocs — s'empilent de haut en bas automatiquement, sans aucune instruction CSS de positionnement. C'est le flux normal à l'œuvre.
Quels éléments sont côte à côte ? Les span.info restent dans le texte du p qui les contient. strong s'insère dans le texte de .bio. Ce sont des éléments en ligne : ils ne forcent jamais de saut de ligne.
Fusion des marges : dans les DevTools, cliquez sur h1 puis observez le Box Model. L'espace sous h1 affiche 30px, et non 50px. Le margin-bottom: 30px de h1 et le margin-top: 20px de .poste ont fusionné : seule la valeur la plus grande a été conservée.
Erreur fréquente : tenter de placer deux blocs côte à côte en jouant uniquement sur les marges. Dans le flux normal pur, deux div ne peuvent jamais être côte à côte. Il faut modifier leur type d'affichage — ce que nous allons voir dans la section 6.2.
Chaque élément HTML a un comportement d'affichage par défaut. Ce comportement est défini par la propriété CSS display, mais il est déjà appliqué par le navigateur via sa feuille de style interne, appelée la user-agent stylesheet. Comprendre les trois grandes catégories d'affichage est indispensable avant de chercher à les modifier.
Un élément block possède quatre caractéristiques fondamentales, toutes liées les unes aux autres :
width, height, margin, padding, border dans toutes les directions.Les éléments HTML qui sont bloc par défaut sont très nombreux : div, p, h1 à h6, ul, ol, li, article, section, header, footer, nav, main, aside, blockquote, pre, form, table, hr, figure, figcaption...
/* Illustration des propriétés d'un élément bloc */
div {
width: 60%; /* On peut réduire la largeur par défaut (100%) */
height: 150px; /* On peut fixer la hauteur */
margin: 20px auto; /* Les marges auto centrent horizontalement */
padding: 20px;
background-color: lightblue;
border: 2px solid navy;
}
/* Un second div sera toujours EN DESSOUS du premier,
même si le premier ne fait que 60% de large. */
div + div {
background-color: lightyellow;
}
Un élément inline (ou en ligne) a un comportement radicalement différent d'un élément bloc. Ses caractéristiques :
width et height. Vous pouvez les écrire, elles n'auront aucun effet.margin-top et margin-bottom) sont ignorées sur les éléments inline.Les éléments HTML inline par défaut : span, a, strong, em, b, i, u, abbr, code, cite, small, sub, sup, label...
/* Illustration : les propriétés ignorées sur les éléments inline */
span {
background-color: yellow;
padding-left: 10px; /* OK : fonctionne */
padding-right: 10px; /* OK : fonctionne */
border-left: 2px solid red; /* OK */
border-right: 2px solid red; /* OK */
width: 300px; /* IGNORÉ */
height: 100px; /* IGNORÉ */
margin-top: 50px; /* IGNORÉ dans le flux */
margin-bottom: 50px; /* IGNORÉ dans le flux */
}
Le type inline-block est un hybride qui combine les avantages des deux modes précédents. C'est une valeur extrêmement utile en pratique.
width, height, margin (y compris vertical), padding et border dans toutes les directions.Aucun élément HTML n'est inline-block par défaut. On obtient ce comportement en appliquant display: inline-block en CSS.
/* Exemple concret : des boutons côte à côte */
.bouton {
display: inline-block; /* La clé : en ligne mais se comporte comme un bloc */
width: 150px;
height: 40px;
padding: 8px 16px;
margin: 5px;
background-color: #2E6DA4;
color: white;
text-align: center;
line-height: 24px; /* Centrage vertical du texte sur une ligne */
border-radius: 4px;
}
Comportement à connaître : les espaces blancs dans le HTML (sauts de ligne entre les balises) créent de petits espaces visuels entre les éléments inline-block. Pour les éliminer, la solution la plus propre est d'appliquer font-size: 0 sur le conteneur parent, puis de restaurer la taille de police sur les enfants.
.conteneur {
font-size: 0; /* Supprime l'espace blanc parasite entre les inline-block */
}
.bouton {
display: inline-block;
font-size: 16px; /* On restaure la taille de police sur les enfants */
}
Objectif : construire un podium visuel affichant trois langages de programmation côte à côte, avec des hauteurs différentes selon leur rang (1er, 2e, 3e place). L'exercice travaille spécifiquement la différence entre block, inline et inline-block, et vous force à choisir le bon type d'affichage pour chaque élément.
Consignes HTML :
podium.html.body, créez une div avec la classe podium.div avec la classe marche. Chacune aura également une classe spécifique : premier, deuxieme, troisieme..marche, placez :
span avec la classe rang contenant respectivement 1, 2, 3.p avec la classe langage contenant le nom du langage : CSS, HTML, JavaScript.podium.css.Consignes CSS :
.podium doit aligner les trois marches côte à côte et en bas (vertical-align: bottom sera utile)..marche doit utiliser display: inline-block pour être côté à côté.150px). Les hauteurs diffèrent : .premier → 180px, .deuxieme → 130px, .troisieme → 100px..rang doit être un élément en ligne mais visuellement mis en valeur (grande taille de police, gras)..langage est un bloc : il doit être centré horizontalement dans la marche.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="podium.css">
<title>Podium des langages</title>
</head>
<body>
<h1>Podium des langages web</h1>
<div class="podium">
<div class="marche deuxieme">
<span class="rang">2</span>
<p class="langage">HTML</p>
</div>
<div class="marche premier">
<span class="rang">1</span>
<p class="langage">CSS</p>
</div>
<div class="marche troisieme">
<span class="rang">3</span>
<p class="langage">JavaScript</p>
</div>
</div>
</body>
</html>
/* podium.css — Correction exercice 2 */
body {
margin: 0;
padding: 40px;
font-family: Arial, sans-serif;
background-color: #f0f4f8;
}
h1 {
text-align: center;
color: #1A3A5C;
margin-bottom: 40px;
}
/*
font-size: 0 sur le conteneur pour supprimer
les espaces blancs parasites entre les inline-block.
vertical-align: bottom aligne les marches par le bas,
ce qui donne l'effet podium avec des hauteurs différentes.
*/
.podium {
font-size: 0;
text-align: center;
}
/*
Chaque marche utilise display: inline-block pour être
côte à côte tout en acceptant width et height.
vertical-align: bottom assure l'alignement par le bas.
*/
.marche {
display: inline-block;
width: 150px;
vertical-align: bottom;
margin: 0 4px;
border-radius: 6px 6px 0 0;
padding-top: 16px;
/* On restaure font-size à 0 annulé sur .podium */
font-size: 16px;
}
/* Hauteurs spécifiques selon le rang */
.premier { height: 180px; background-color: #F0C040; }
.deuxieme { height: 130px; background-color: #B0B8C1; }
.troisieme { height: 100px; background-color: #CD7F32; }
/*
.rang est un <span> : élément inline par défaut.
On ne change pas son display, mais on le stylise fortement.
Il s'insère dans le flux du texte à l'intérieur de .marche.
*/
.rang {
font-size: 48px;
font-weight: bold;
color: white;
display: block; /* On le passe en bloc pour le centrer facilement */
text-align: center;
}
/*
.langage est un <p> : élément bloc par défaut.
Il prend naturellement toute la largeur de .marche.
text-align: center suffit à centrer son contenu.
*/
.langage {
font-size: 14px;
font-weight: bold;
color: white;
text-align: center;
margin: 8px 0 0 0;
text-transform: uppercase;
letter-spacing: 1px;
}
Pourquoi display: inline-block sur .marche ? Parce que les div sont des blocs par défaut : ils s'empileraient verticalement. inline-block leur permet d'être côte à côte tout en conservant la possibilité de définir width et height. Avec display: inline, ils ne pourraient pas avoir de hauteur définie.
Pourquoi vertical-align: bottom ? Sans cette propriété, les éléments inline-block s'alignent par leur ligne de base (baseline), ce qui donne un résultat désorganisé quand les hauteurs diffèrent. bottom aligne tous les blocs par leur bord inférieur, ce qui crée l'effet podium attendu.
Pourquoi font-size: 0 sur .podium ? Les espaces et sauts de ligne entre les balises div dans le HTML se traduisent par un petit espace visuel entre les marches. En passant font-size: 0 sur le parent, ces espaces blancs — qui sont traités comme du texte — disparaissent. Il faut ensuite restaurer font-size sur les enfants.
L'ordre dans le HTML : notez que la marche du 2e est placée en premier dans le HTML, avant celle du 1er. C'est intentionnel pour reproduire l'ordre visuel d'un vrai podium (2e à gauche, 1er au centre, 3e à droite). Le flux lit le HTML de gauche à droite, donc l'ordre du code détermine l'ordre à l'écran.
Erreur fréquente : utiliser display: block sur .marche en pensant que cela suffit. Deux blocs ne peuvent pas être côte à côte dans le flux normal — ils s'empileraient immédiatement.
Objectif : créer une galerie de badges de compétences qui s'affichent côte à côte et passent automatiquement à la ligne quand l'espace manque. Chaque badge affiche une icône textuelle (emoji ou initiale), un nom de compétence et un niveau (débutant, intermédiaire, expert). L'exercice vous oblige à combiner intelligemment éléments blocs, inline et inline-block au sein d'une même structure.
Consignes HTML :
badges.html.body, créez un titre h1 avec le texte Mes compétences.div avec la classe galerie.div avec la classe badge. Chaque badge contiendra :
span avec la classe icone contenant une initiale ou un symbole au choix (par exemple : H, C, JS, PY, SQL, GIT).p avec la classe nom contenant le nom de la compétence (HTML, CSS, JavaScript, Python, SQL, Git).span avec la classe niveau contenant le niveau. Attribuez les classes supplémentaires expert, intermediaire ou debutant selon le niveau choisi.badges.css.Consignes CSS :
.badge doit être en inline-block pour que les badges se placent côte à côte et passent naturellement à la ligne suivante quand la fenêtre est trop étroite..badge a une largeur fixe de 140px, un padding de 20px, des coins arrondis et une ombre légère (box-shadow)..icone doit être convertie en élément bloc pour occuper toute la largeur du badge et être centrée. Donnez-lui une grande taille de police (36px), un fond coloré et une hauteur fixe de 60px avec line-height: 60px pour centrer verticalement le texte..nom est déjà un bloc : centrez son texte, mettez-le en gras..niveau doit être converti en bloc pour passer sous le nom. Sa couleur de texte doit changer selon la classe : .expert en vert foncé, .intermediaire en orange, .debutant en gris..badge, ajoutez un léger effet de remontée avec transform: translateY(-4px) et une transition fluide.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="badges.css">
<title>Galerie de badges</title>
</head>
<body>
<h1>Mes compétences</h1>
<div class="galerie">
<div class="badge">
<span class="icone">H</span>
<p class="nom">HTML</p>
<span class="niveau expert">Expert</span>
</div>
<div class="badge">
<span class="icone">C</span>
<p class="nom">CSS</p>
<span class="niveau expert">Expert</span>
</div>
<div class="badge">
<span class="icone">JS</span>
<p class="nom">JavaScript</p>
<span class="niveau intermediaire">Intermédiaire</span>
</div>
<div class="badge">
<span class="icone">PY</span>
<p class="nom">Python</p>
<span class="niveau intermediaire">Intermédiaire</span>
</div>
<div class="badge">
<span class="icone">SQL</span>
<p class="nom">SQL</p>
<span class="niveau debutant">Débutant</span>
</div>
<div class="badge">
<span class="icone">GIT</span>
<p class="nom">Git</p>
<span class="niveau debutant">Débutant</span>
</div>
</div>
</body>
</html>
/* badges.css — Correction exercice 3 */
body {
margin: 0;
padding: 40px;
font-family: Arial, sans-serif;
background-color: #f4f6f9;
}
h1 {
color: #1A3A5C;
margin-bottom: 30px;
}
/*
La galerie est un bloc (div par défaut) qui occupe toute la largeur.
Pas besoin de modifier son display : ses enfants .badge en inline-block
vont naturellement se placer côte à côte et revenir à la ligne
automatiquement quand l'espace est insuffisant.
*/
.galerie {
/* font-size: 0 pour supprimer les espaces blancs entre les badges */
font-size: 0;
}
/*
Chaque badge est inline-block : côte à côte, mais avec width/height/padding.
vertical-align: top pour les aligner par le haut (comportement attendu
pour une galerie de cartes de même hauteur).
*/
.badge {
display: inline-block;
vertical-align: top;
width: 140px;
padding: 0 0 16px 0;
margin: 10px;
background-color: #ffffff;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden; /* Pour que l'icône respecte le border-radius en haut */
/* On restaure la font-size annulée par le parent */
font-size: 14px;
/* Transition pour l'effet de survol */
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.badge:hover {
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
}
/*
.icone est un <span> : inline par défaut.
On le passe en display: block pour qu'il occupe toute la largeur
du badge et qu'on puisse lui donner une hauteur et le centrer.
*/
.icone {
display: block;
height: 60px;
line-height: 60px; /* Centrage vertical du texte */
text-align: center; /* Centrage horizontal */
font-size: 24px;
font-weight: bold;
color: white;
background-color: #2E6DA4;
letter-spacing: 1px;
}
/*
.nom est un <p> : déjà bloc par défaut.
Il prend toute la largeur du badge naturellement.
*/
.nom {
text-align: center;
font-weight: bold;
color: #1A3A5C;
margin: 12px 0 6px 0;
font-size: 15px;
}
/*
.niveau est un <span> : inline par défaut.
On le passe en display: block pour le placer sous .nom
et pouvoir le centrer facilement.
*/
.niveau {
display: block;
text-align: center;
font-size: 12px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Couleurs selon le niveau */
.expert { color: #1E6B2E; }
.intermediaire { color: #B05A00; }
.debutant { color: #888888; }
Pourquoi display: block sur .icone et .niveau ? Ces deux éléments sont des span : inline par défaut. En les laissant inline, ils resteraient sur la même ligne que le contenu adjacent et ne pourraient pas être centrés avec text-align. En les passant en block, ils prennent toute la largeur de leur parent et se placent chacun sur leur propre ligne, ce qui est exactement le comportement attendu pour une carte structurée verticalement.
Le retour à la ligne automatique des inline-block : c'est l'un des grands avantages de ce type d'affichage. Contrairement à Flexbox ou Grid (que vous verrez plus tard), les éléments inline-block se comportent exactement comme des mots dans un texte : ils passent à la ligne suivante quand l'espace horizontal est épuisé. Cela rend la galerie naturellement responsive sans aucune media query.
vertical-align: top vs bottom : dans l'exercice précédent (podium), on utilisait bottom pour aligner par le bas. Ici, top est le bon choix : on veut que les badges s'alignent par leur bord supérieur, comme des cartes posées côte à côte sur une table.
overflow: hidden sur .badge : cette propriété force les coins arrondis (border-radius) à s'appliquer aussi à l'élément .icone qui se trouve en haut du badge. Sans elle, le fond coloré de l'icône dépasserait des coins arrondis de la carte.
Erreur fréquente : oublier de restaurer font-size sur .badge après avoir mis font-size: 0 sur .galerie. Sans cette restauration, tout le texte à l'intérieur des badges disparaît.
La propriété CSS display est l'une des plus importantes de tout le langage CSS. Elle permet de modifier le comportement d'affichage natif de n'importe quel élément HTML. C'est elle qui détermine comment un élément participe au flux et comment il interagit avec ses voisins. Vous venez de voir les trois grandes catégories d'affichage — il est maintenant temps de comprendre comment les manipuler activement et dans quels contextes précis cela s'avère indispensable.
La propriété display accepte de nombreuses valeurs. Dans le cadre de ce module, nous nous concentrons sur les valeurs fondamentales qui agissent sur le flux du document. Les valeurs flex et grid seront traitées dans des modules dédiés.
element { display: block; }
/* => L'élément se comporte comme un bloc : nouvelle ligne, 100% de largeur,
accepte width / height / margin / padding dans toutes les directions. */
element { display: inline; }
/* => L'élément se comporte comme du texte en ligne : pas de saut de ligne,
ignore width et height, marges verticales ignorées. */
element { display: inline-block; }
/* => Hybride : en ligne mais accepte width / height / margin vertical. */
element { display: none; }
/* => L'élément est complètement retiré du flux ET invisible.
Il ne prend plus aucune place dans la page. */
element { display: list-item; }
/* => Se comporte comme un <li>, avec puce ou numéro automatique. */
element { display: table; }
/* => Se comporte comme un <table> HTML, avec ses règles d'alignement. */
La différence entre display: none et visibility: hidden est fondamentale et source d'erreurs fréquentes. Ces deux propriétés cachent un élément, mais de manière radicalement différente :
/* display: none retire COMPLÈTEMENT l'élément du flux */
.cache-sans-espace {
display: none;
}
/* L'élément n'est plus là visuellement ET physiquement.
Les éléments suivants remontent pour combler le vide. */
/* visibility: hidden cache l'élément mais conserve son espace dans le flux */
.cache-avec-espace {
visibility: hidden;
}
/* L'élément est invisible mais son espace est préservé.
Les éléments voisins ne bougent pas. */
/* opacity: 0 rend l'élément transparent mais garde espace ET interactivité */
.transparent {
opacity: 0;
}
/* Invisible, espace préservé, ET il réagit toujours au clic et au hover. */
À retenir : display: none = l'élément n'existe plus visuellement ni physiquement. visibility: hidden = l'élément existe encore dans la mise en page, il est juste invisible. opacity: 0 = l'élément existe, est invisible, et reste interactif.
Modifier la propriété display est une technique quotidienne en CSS. Voici les scénarios les plus fréquents que vous rencontrerez en pratique.
Cas 1 : transformer un lien en bloc cliquable.
Par défaut, un <a> est inline : seul le texte est cliquable. En le passant en display: block, toute la surface rectangulaire devient cliquable. C'est indispensable pour les menus où l'on veut une grande zone de clic confortable.
.menu-lateral a {
display: block; /* Toute la largeur du menu devient cliquable */
padding: 12px 20px;
color: #ffffff;
text-decoration: none;
background-color: #1A3A5C;
border-bottom: 1px solid #2E6DA4;
transition: background-color 0.2s ease;
}
.menu-lateral a:hover {
background-color: #2E6DA4;
}
Cas 2 : afficher des éléments de liste côte à côte.
Les <li> sont des blocs par défaut. Pour construire une navigation horizontale à partir d'une liste <ul>, on les passe en inline-block. La liste conserve sa sémantique HTML tout en s'affichant horizontalement.
nav ul {
list-style: none;
margin: 0;
padding: 0;
font-size: 0; /* Suppression des espaces blancs parasites */
}
nav li {
display: inline-block;
font-size: 16px; /* On restaure la taille de police */
}
nav li a {
display: block; /* Le lien remplit tout le li : zone de clic maximale */
padding: 15px 20px;
color: white;
text-decoration: none;
}
Cas 3 : réduire un bloc à la taille de son contenu.
Un <div> est bloc par défaut : il prend toute la largeur disponible. Pour créer un badge ou une étiquette à partir d'un <div>, on le passe en display: inline-block afin qu'il ne prenne que la place de son contenu.
/* Sans modification : le div s'étire sur toute la ligne */
.badge { background-color: #2E6DA4; padding: 4px 12px; }
/* Avec inline-block : le div rétrécit à la taille du texte */
.badge {
display: inline-block;
background-color: #2E6DA4;
color: white;
padding: 4px 12px;
border-radius: 20px;
}
Cas 4 : masquer un élément selon la taille d'écran.
Combiné aux media queries, display: none permet d'afficher ou masquer des éléments selon le contexte. C'est une technique fondamentale du design responsive.
.menu-hamburger {
display: none; /* Masqué sur grand écran */
}
@media (max-width: 768px) {
.menu-hamburger {
display: block; /* Visible sur mobile */
}
.nav-bureau {
display: none; /* Masquée sur mobile */
}
}
Objectif : construire la barre de navigation horizontale d'un blog à partir d'une liste HTML sémantique. L'exercice cible précisément les deux conversions de display les plus courantes en navigation : les <li> de blocs à inline-block, et les <a> d'inline à block.
Consignes HTML :
blog-nav.html.<header> contenant un <h1> avec le nom du blog de votre choix.<nav> contenant une <ul> avec quatre <li>. Chaque <li> contient un <a href="#"> avec les rubriques : Accueil, Articles, À propos, Contact.<nav>, ajoutez un <p> avec un court texte de bienvenue.blog-nav.css.Consignes CSS :
<header> a un fond sombre, du padding et le <h1> est blanc et sans marge.<nav> a un fond d'une couleur légèrement différente du header.<ul> : retirez les puces et marges par défaut, appliquez font-size: 0.<li> : passez-les en display: inline-block et restaurez font-size: 15px.<a> : passez-les en display: block, ajoutez du padding, retirez le soulignement, colorez le texte en blanc. Ajoutez un changement de couleur de fond au survol.<p> sous la nav doit avoir du padding et une taille de police lisible.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="blog-nav.css">
<title>Mon Blog</title>
</head>
<body>
<header>
<h1>Le Blog du Dev</h1>
</header>
<nav>
<ul>
<li><a href="#">Accueil</a></li>
<li><a href="#">Articles</a></li>
<li><a href="#">À propos</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
<p class="intro">Bienvenue sur ce blog dédié au développement web. Retrouvez ici des articles, des tutoriels et des ressources pour progresser en HTML, CSS et JavaScript.</p>
</body>
</html>
/* blog-nav.css — Correction exercice 4 */
body {
margin: 0;
font-family: Arial, sans-serif;
}
header {
background-color: #1A3A5C;
padding: 24px 30px;
}
header h1 {
color: #ffffff;
margin: 0;
font-size: 26px;
}
nav {
background-color: #2E6DA4;
}
nav ul {
list-style: none; /* Supprime les puces par défaut */
margin: 0;
padding: 0 20px;
font-size: 0; /* Supprime les espaces blancs entre les li inline-block */
}
/*
Les <li> sont des blocs par défaut : ils s'empileraient verticalement.
display: inline-block les place côte à côte.
*/
nav li {
display: inline-block;
font-size: 15px; /* Restauration après le font-size: 0 du parent */
}
/*
Les <a> sont inline par défaut : seul le texte est cliquable.
display: block étend la zone cliquable à tout le padding.
*/
nav li a {
display: block;
padding: 14px 18px;
color: #ffffff;
text-decoration: none;
transition: background-color 0.2s ease;
}
nav li a:hover {
background-color: #1A3A5C;
}
.intro {
padding: 24px 30px;
font-size: 16px;
line-height: 1.7;
color: #333333;
}
display: inline-block sur les <li> : c'est l'unique instruction qui transforme la liste verticale en barre de navigation horizontale. Sans elle, chaque <li> est un bloc et s'empile sous le précédent.
display: block sur les <a> : un lien inline n'est cliquable que sur son texte. En le passant en bloc, il remplit tout son <li> parent, padding compris. La zone de clic devient confortable. C'est une bonne pratique systématique pour tous les liens de navigation.
font-size: 0 sur <ul> puis restauration sur <li> : sans font-size: 0, un espace de quelques pixels apparaît entre chaque lien à cause des sauts de ligne dans le HTML. Cet espace est traité comme un caractère de texte. En annulant la taille de police sur le parent, ces espaces disparaissent. Il faut impérativement la restaurer sur les <li> sinon le texte des liens est invisible.
Erreur fréquente : appliquer display: inline-block sur les <a> au lieu des <li>. Cela fonctionne partiellement mais le <li> reste bloc et enveloppe le lien sans prendre la bonne taille — le résultat est visuellement incorrect.
Objectif : créer une liste de catégories d'articles affichées sous forme d'étiquettes colorées. L'exercice travaille spécifiquement la conversion d'un <div> en inline-block pour le réduire à la taille de son contenu (comportement de badge), et la mise en évidence visuelle d'une catégorie active avec display et des classes CSS.
Consignes HTML :
categories.html.<h2> avec le texte Filtrer par catégorie.<div> avec la classe liste-categories contenant six <div> avec la classe categorie. Donnez à chacun une classe supplémentaire parmi : cat-tech, cat-design, cat-code, cat-actu, cat-video, cat-podcast. Les textes : Tech, Design, Code, Actualité, Vidéo, Podcast.active sur la catégorie Tech pour simuler la catégorie sélectionnée.<p class="info-masquee"> avec le texte Aucune catégorie sélectionnée.
categories.css.Consignes CSS :
.liste-categories : ajoutez un peu de padding vertical..categorie : passez-les en display: inline-block pour qu'ils se comportent comme des badges. Donnez-leur un padding, un border-radius, une border, une couleur de texte et un fond neutre. Ajoutez un margin pour les espacer et un effet de survol.cat-tech, cat-design, etc.) uniquement au survol ou à l'état .active..active doit appliquer un fond coloré et du texte blanc, sans bordure.<p class="info-masquee"> doit être masqué avec visibility: hidden. Observez qu'il laisse un espace vide sous les étiquettes — c'est la différence avec display: none.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="categories.css">
<title>Catégories</title>
</head>
<body>
<h2>Filtrer par catégorie</h2>
<div class="liste-categories">
<div class="categorie cat-tech active">Tech</div>
<div class="categorie cat-design">Design</div>
<div class="categorie cat-code">Code</div>
<div class="categorie cat-actu">Actualité</div>
<div class="categorie cat-video">Vidéo</div>
<div class="categorie cat-podcast">Podcast</div>
</div>
<p class="info-masquee">Aucune catégorie sélectionnée.</p>
</body>
</html>
/* categories.css — Correction exercice 5 */
body {
margin: 0;
padding: 40px;
font-family: Arial, sans-serif;
background-color: #f5f5f5;
}
h2 {
color: #1A3A5C;
margin-bottom: 16px;
}
.liste-categories {
padding: 10px 0;
}
/*
Les .categorie sont des <div> : blocs par défaut.
Sans display: inline-block, chaque catégorie s'étirerait
sur toute la largeur de la page — comportement inadapté pour un badge.
inline-block les réduit à la taille de leur contenu.
*/
.categorie {
display: inline-block;
padding: 8px 18px;
margin: 4px;
border-radius: 20px;
border: 2px solid #cccccc;
color: #555555;
background-color: #ffffff;
font-size: 14px;
font-weight: bold;
cursor: pointer;
transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
/* Survol générique */
.categorie:hover {
border-color: #2E6DA4;
color: #2E6DA4;
}
/* Couleurs spécifiques au survol par catégorie */
.cat-tech:hover { border-color: #2E6DA4; color: #2E6DA4; }
.cat-design:hover { border-color: #8E44AD; color: #8E44AD; }
.cat-code:hover { border-color: #1E6B2E; color: #1E6B2E; }
.cat-actu:hover { border-color: #C0392B; color: #C0392B; }
.cat-video:hover { border-color: #E67E22; color: #E67E22; }
.cat-podcast:hover { border-color: #16A085; color: #16A085; }
/* État actif : fond coloré, texte blanc, pas de bordure visible */
.cat-tech.active { background-color: #2E6DA4; color: #ffffff; border-color: #2E6DA4; }
.cat-design.active { background-color: #8E44AD; color: #ffffff; border-color: #8E44AD; }
.cat-code.active { background-color: #1E6B2E; color: #ffffff; border-color: #1E6B2E; }
.cat-actu.active { background-color: #C0392B; color: #ffffff; border-color: #C0392B; }
.cat-video.active { background-color: #E67E22; color: #ffffff; border-color: #E67E22; }
.cat-podcast.active { background-color: #16A085; color: #ffffff; border-color: #16A085; }
/*
visibility: hidden masque l'élément mais préserve son espace dans le flux.
Comparez avec display: none : en passant à display: none,
le paragraphe disparaît ET les étiquettes remontent.
Avec visibility: hidden, l'espace est réservé et rien ne bouge.
*/
.info-masquee {
visibility: hidden;
font-size: 14px;
color: #888888;
margin-top: 12px;
}
display: inline-block sur .categorie : c'est le cœur de l'exercice. Un <div> est un bloc — il prendrait toute la largeur de la page. inline-block le contraint à la taille de son texte, exactement comme se comporte un <span>, mais en lui conservant la capacité d'avoir padding, border et border-radius qui s'appliquent proprement dans toutes les directions.
visibility: hidden sur .info-masquee : observez attentivement dans le navigateur : sous les étiquettes, un espace vide subsiste là où se trouverait le paragraphe s'il était visible. C'est précisément la différence avec display: none. Essayez de remplacer visibility: hidden par display: none : les étiquettes descendent légèrement car l'espace réservé disparaît. Ce comportement est important à connaître pour les interfaces où la stabilité visuelle de la mise en page est cruciale.
Les classes combinées .cat-tech.active : cette notation CSS cible un élément qui possède à la fois la classe cat-tech ET la classe active. Pas d'espace entre les deux noms de classe — un espace signifierait un descendant, ce qui serait une sélection totalement différente.
Erreur fréquente : laisser display: block sur .categorie et s'étonner que les badges s'empilent verticalement. Le changement de display est ici la seule instruction qui transforme la mise en page de verticale en horizontale.
La propriété position est celle qui vous donne un contrôle précis sur l'emplacement d'un élément dans la page, indépendamment du flux normal. Jusqu'ici, les éléments se plaçaient selon les règles du flux — les uns sous les autres ou côte à côte. Avec position, vous pouvez placer un élément exactement où vous le souhaitez, le fixer à l'écran, ou le décaler par rapport à sa position d'origine.
position fonctionne toujours en tandem avec quatre propriétés de décalage : top, right, bottom et left. Ces propriétés n'ont aucun effet si position vaut static (la valeur par défaut). Dès que position prend une autre valeur, elles entrent en jeu pour indiquer où placer l'élément.
element {
position: static; /* Valeur par défaut : l'élément suit le flux normal */
position: relative; /* Décalé par rapport à sa position d'origine dans le flux */
position: absolute; /* Positionné par rapport à son ancêtre positionné le plus proche */
position: fixed; /* Positionné par rapport à la fenêtre du navigateur */
position: sticky; /* Suit le défilement jusqu'à un seuil, puis se fixe */
}
position: static est la valeur par défaut de tous les éléments HTML. Vous n'avez jamais besoin de l'écrire explicitement, sauf pour annuler un positionnement défini ailleurs (par exemple dans une feuille de style tierce que vous souhaitez neutraliser).
/* Ces deux règles sont strictement équivalentes */
div { }
div { position: static; }
/* Cas d'usage réel : neutraliser un positionnement hérité */
.remise-a-zero {
position: static; /* Annule un position: relative ou absolute défini ailleurs */
}
Un élément static ignore complètement les propriétés top, right, bottom et left. Si vous les écrivez, elles n'auront aucun effet. C'est le comportement le plus courant, celui que vous avez utilisé dans tous les exercices précédents sans le savoir.
position: relative est le premier type de positionnement qui sort de l'ordinaire. Il a deux effets distincts qu'il est important de bien comprendre séparément.
Premier effet : décaler l'élément visuellement.
L'élément est déplacé par rapport à la position qu'il aurait occupée normalement dans le flux. Les propriétés top, right, bottom et left définissent ce décalage.
.boite {
position: relative;
top: 20px; /* Décale de 20px vers le bas par rapport à sa position naturelle */
left: 30px; /* Décale de 30px vers la droite */
}
Second effet — et c'est le plus important en pratique : créer un contexte de positionnement.
Un élément en position: relative devient le point de référence pour tous ses descendants en position: absolute. C'est l'usage le plus fréquent du positionnement relatif, et nous y reviendrons en détail dans la section suivante.
Point crucial : contrairement à float ou position: absolute, un élément en position: relative reste dans le flux normal. L'espace qu'il occupait originellement est préservé. Les autres éléments ne le voient pas se déplacer — ils ignorent son décalage visuel.
/* Illustration : l'espace original est conservé */
.paragraphe-decale {
position: relative;
top: 40px;
background-color: lightblue;
}
/*
Visuellement, .paragraphe-decale descend de 40px.
Mais le paragraphe suivant se positionne comme si
.paragraphe-decale n'avait pas bougé.
Un espace vide apparaît là où il était originellement.
*/
position: absolute retire complètement l'élément du flux normal. Les autres éléments se comportent comme s'il n'existait plus, et comblent l'espace qu'il aurait occupé.
L'élément est ensuite positionné par rapport à son ancêtre positionné le plus proche, c'est-à-dire le premier ancêtre qui a une valeur de position différente de static. Si aucun ancêtre n'est positionné, l'élément se positionne par rapport au <body>.
/* Schéma classique : conteneur relatif + enfant absolu */
.conteneur {
position: relative; /* Devient le point de référence pour les enfants absolus */
width: 400px;
height: 300px;
background-color: #e8f0fe;
border: 2px solid #2E6DA4;
}
.etiquette {
position: absolute; /* Sort du flux, se positionne par rapport à .conteneur */
top: 10px; /* À 10px du bord supérieur de .conteneur */
right: 10px; /* À 10px du bord droit de .conteneur */
background-color: #C0392B;
color: white;
padding: 4px 10px;
font-size: 12px;
border-radius: 3px;
}
<!-- Structure HTML correspondante -->
<div class="conteneur">
<span class="etiquette">NOUVEAU</span>
<p>Le contenu de la carte. L'étiquette "NOUVEAU" est positionnée
en haut à droite de la carte, sans perturber ce texte.</p>
</div>
Ce schéma position: relative sur le parent + position: absolute sur l'enfant est l'un des patterns les plus utilisés en CSS. Il sert à créer des badges, des tooltips, des overlays, des icônes superposées à des images, etc.
Attention au piège de l'ancêtre : si vous oubliez de mettre
position: relativesur le conteneur parent, l'élément absolu se positionnera par rapport au<body>entier, ce qui donnera presque toujours un résultat inattendu.
/* Positionnement aux quatre coins d'un conteneur */
.coin-haut-gauche { top: 0; left: 0; }
.coin-haut-droit { top: 0; right: 0; }
.coin-bas-gauche { bottom: 0; left: 0; }
.coin-bas-droit { bottom: 0; right: 0; }
/* Couvrir entièrement le conteneur parent (overlay) */
.overlay {
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
background-color: rgba(0, 0, 0, 0.5);
}
position: fixed retire également l'élément du flux normal, mais le positionne par rapport à la fenêtre du navigateur (le viewport), et non par rapport à un ancêtre. L'élément reste donc visible à la même position même lorsque l'utilisateur fait défiler la page.
/* Barre de navigation fixe en haut de page */
.navbar-fixe {
position: fixed;
top: 0;
left: 0;
width: 100%; /* S'étend sur toute la largeur de la fenêtre */
background-color: #1A3A5C;
color: white;
padding: 15px 30px;
z-index: 100; /* Passe au-dessus des autres éléments */
}
/*
Problème classique avec position: fixed :
la navbar sort du flux et se superpose au contenu.
Il faut compenser en ajoutant un padding-top au body
égal à la hauteur de la navbar.
*/
body {
padding-top: 60px; /* Valeur égale à la hauteur de la navbar */
}
/* Bouton "retour en haut" fixe en bas à droite */
.btn-retour-haut {
position: fixed;
bottom: 30px;
right: 30px;
width: 44px;
height: 44px;
background-color: #2E6DA4;
color: white;
text-align: center;
line-height: 44px;
border-radius: 50%;
text-decoration: none;
font-size: 20px;
}
Les usages typiques de position: fixed sont : barres de navigation persistantes, boutons flottants, bandeaux de cookies, chatbots, panier d'achat flottant dans un e-commerce.
position: sticky est un hybride entre relative et fixed. L'élément se comporte comme un élément relatif — il suit le défilement normal de la page — jusqu'à ce qu'il atteigne un seuil défini par top, bottom, left ou right. À partir de ce seuil, il se comporte comme un élément fixe et reste visible à cette position pendant le défilement.
/* En-tête de tableau qui reste visible au défilement */
thead th {
position: sticky;
top: 0; /* Se colle au bord supérieur de la fenêtre */
background-color: #1A3A5C;
color: white;
padding: 12px 16px;
z-index: 10;
}
/* Barre latérale qui suit le défilement */
.sidebar {
position: sticky;
top: 20px; /* La sidebar se colle à 20px du haut de la fenêtre */
height: fit-content;
}
Condition indispensable : pour que position: sticky fonctionne, l'élément doit avoir de l'espace pour défiler à l'intérieur de son conteneur parent. Si le parent n'est pas plus haut que l'élément sticky lui-même, l'effet ne se produit pas. De plus, le parent ne doit pas avoir overflow: hidden ou overflow: auto, ce qui annulerait le comportement sticky.
Contrairement à position: fixed, un élément sticky reste dans le flux normal. Il n'y a pas besoin de compenser son espace avec du padding sur le body.
Objectif : construire une carte produit avec un badge "Nouveauté" positionné en haut à droite de la carte. L'exercice travaille le pattern fondamental position: relative sur le parent + position: absolute sur l'enfant, qui est le schéma de positionnement le plus utilisé en CSS au quotidien.
Consignes HTML :
carte-produit.html.<div> avec la classe carte. À l'intérieur, placez :
<span> avec la classe badge et le texte Nouveauté.<div> avec la classe image-produit (simulera l'image).<h2> avec un nom de produit.<p> avec la classe prix (ex : 49,00 €).<p> avec une courte description.carte-produit.css.Consignes CSS :
.carte est centrée sur la page (max-width: 320px, margin: 40px auto). Donnez-lui un fond blanc, une ombre légère (box-shadow) et des coins arrondis. Elle doit avoir position: relative pour servir de référence au badge..image-produit est un bloc avec height: 200px et un fond coloré..badge doit être positionné en absolute, ancré en haut à droite de la carte (top: 12px, right: 12px). Stylisez-le avec un fond rouge, du texte blanc, du padding et un border-radius.h2 et le .prix ont simplement du padding horizontal et une couleur.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="carte-produit.css">
<title>Carte produit</title>
</head>
<body>
<div class="carte">
<span class="badge">Nouveauté</span>
<div class="image-produit"></div>
<h2>Sac en cuir véritable</h2>
<p class="prix">49,00 €</p>
<p class="description">Fabriqué à la main, disponible en trois coloris. Livraison offerte dès 60€ d'achat.</p>
</div>
</body>
</html>
/* carte-produit.css — Correction exercice 6 */
body {
margin: 0;
background-color: #f0f4f8;
font-family: Arial, sans-serif;
}
/*
position: relative sur .carte est indispensable.
Sans elle, le badge se positionnerait par rapport au <body>
et non par rapport à la carte. C'est l'erreur la plus fréquente.
*/
.carte {
position: relative;
max-width: 320px;
margin: 40px auto;
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.image-produit {
width: 100%;
height: 200px;
background-color: #c8daf0;
}
/*
position: absolute retire le badge du flux.
Il se positionne par rapport à .carte (son ancêtre positionné).
top et right définissent sa distance aux bords de .carte.
*/
.badge {
position: absolute;
top: 12px;
right: 12px;
background-color: #C0392B;
color: #ffffff;
font-size: 12px;
font-weight: bold;
padding: 5px 12px;
border-radius: 20px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
h2 {
padding: 16px 20px 4px;
font-size: 18px;
color: #1A3A5C;
margin: 0;
}
.prix {
padding: 0 20px;
font-size: 20px;
font-weight: bold;
color: #C0392B;
margin: 6px 0;
}
.description {
padding: 0 20px 20px;
font-size: 13px;
color: #666666;
line-height: 1.6;
margin: 0;
}
Pourquoi position: relative sur .carte ? C'est le point le plus important de l'exercice. Sans cette déclaration, le navigateur cherche l'ancêtre positionné le plus proche du badge — et ne trouvant rien, il remonte jusqu'au <body>. Le badge se placerait alors à 12px du coin supérieur droit de la page entière, pas de la carte. C'est l'erreur numéro un avec position: absolute.
Le badge sort du flux : le <span> avec position: absolute n'occupe plus d'espace dans le flux. Le <h2> qui le suit dans le HTML se positionne comme si le badge n'existait pas. C'est pourquoi l'image et le titre ne sont pas décalés par la présence du badge.
overflow: hidden sur .carte : cette propriété fait que l'image respecte les coins arrondis de la carte. Sans elle, le fond coloré de .image-produit déborderait des coins arrondis dans les angles supérieurs.
Erreur fréquente : mettre position: absolute sur .carte au lieu de position: relative. La carte sortirait alors du flux et se positionnerait par rapport au body — la mise en page serait complètement cassée.
Objectif : construire une page avec une barre de navigation fixe qui reste visible lors du défilement. L'exercice travaille position: fixed et la compensation du chevauchement avec le contenu — un problème concret que tout développeur rencontre dès la première utilisation de ce type de positionnement.
Consignes HTML :
nav-fixe.html.<nav> avec la classe navbar contenant un <span> avec la classe logo (texte : MonSite) et une <ul> avec trois <li> / <a> : Accueil, Services, Contact.<main> avec la classe contenu contenant trois <section>, chacune avec un <h2> et deux <p> de texte fictif suffisamment long pour forcer le défilement de la page.nav-fixe.css.Consignes CSS :
.navbar doit être en position: fixed, collée en haut de la fenêtre (top: 0, left: 0), avec width: 100%. Donnez-lui un fond sombre, du padding, et un z-index: 100..navbar, affichez le .logo et la <ul> côte à côte : le logo à gauche, la liste à droite. Utilisez display: inline-block et float: right sur la liste (ou la technique de votre choix avec ce que vous avez vu jusqu'ici).<li> de la navbar sont en inline-block, les <a> en block. Retirez puces et soulignements..contenu doit avoir un padding-top égal à la hauteur de la navbar (environ 60px) pour que le premier titre ne soit pas masqué derrière elle.<section> a du padding et une bordure basse pour la séparer visuellement.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="nav-fixe.css">
<title>Navigation fixe</title>
</head>
<body>
<nav class="navbar">
<span class="logo">MonSite</span>
<ul>
<li><a href="#">Accueil</a></li>
<li><a href="#">Services</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
<main class="contenu">
<section>
<h2>Bienvenue</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</section>
<section>
<h2>Nos services</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident deserunt mollit anim id est laborum.</p>
</section>
<section>
<h2>Contactez-nous</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt.</p>
</section>
</main>
</body>
</html>
/* nav-fixe.css — Correction exercice 7 */
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: Arial, sans-serif;
color: #2c2c2c;
}
/*
position: fixed sort la navbar du flux et la colle à la fenêtre.
Elle reste visible même quand l'utilisateur fait défiler la page.
width: 100% est nécessaire car un élément fixe ne prend pas
automatiquement toute la largeur — il faut le préciser.
*/
.navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
background-color: #1A3A5C;
padding: 0 30px;
height: 60px;
line-height: 60px;
z-index: 100;
font-size: 0; /* Supprime l'espace blanc entre .logo et ul */
}
.logo {
display: inline-block;
font-size: 20px;
font-weight: bold;
color: #ffffff;
letter-spacing: 1px;
}
.navbar ul {
display: inline-block;
float: right;
list-style: none;
margin: 0;
padding: 0;
font-size: 0;
}
.navbar li {
display: inline-block;
font-size: 14px;
}
.navbar li a {
display: block;
padding: 0 16px;
color: #ffffff;
text-decoration: none;
transition: background-color 0.2s ease;
}
.navbar li a:hover {
background-color: #2E6DA4;
}
/*
Sans ce padding-top, le premier <h2> du contenu se placerait
DERRIÈRE la navbar fixe et serait partiellement masqué.
La valeur doit être égale ou légèrement supérieure à la hauteur
de la navbar (ici 60px).
*/
.contenu {
padding-top: 60px;
}
section {
padding: 40px 30px;
border-bottom: 1px solid #e0e0e0;
max-width: 800px;
margin: 0 auto;
}
section h2 {
color: #1A3A5C;
margin-top: 0;
}
section p {
line-height: 1.8;
color: #444444;
}
position: fixed sort la navbar du flux : la navbar n'occupe plus d'espace dans la page. Sans le padding-top: 60px sur .contenu, le premier titre serait masqué derrière la navbar. C'est le problème le plus fréquent rencontré avec position: fixed : on fixe la nav, on défile, et on constate que le début du contenu est inaccessible.
width: 100% est obligatoire : un élément fixe ou absolu perd sa largeur automatique de 100%. Il prend uniquement la place de son contenu. Si vous oubliez width: 100%, la navbar ne couvrira que la largeur de son texte.
z-index: 100 : sans cette propriété, certains éléments de la page pourraient passer par-dessus la navbar lors du défilement. Le z-index garantit que la navbar reste toujours au premier plan. Nous détaillerons le fonctionnement du z-index dans la section 6.5.
position: sticky vs position: fixed : si vous remplacez fixed par sticky avec top: 0 dans cet exercice, vous observerez que la navbar suit le défilement jusqu'à atteindre le haut de la fenêtre, puis se colle. La différence majeure : avec sticky, le padding-top sur .contenu n'est plus nécessaire car l'élément reste dans le flux.
Dès que vous commencez à utiliser position: absolute, position: fixed ou position: relative avec des décalages, des éléments peuvent se retrouver à se superposer. Le navigateur doit alors décider lequel s'affiche par-dessus l'autre. Cette décision n'est pas arbitraire : elle obéit à des règles précises que vous allez maintenant maîtriser.
Par défaut, quand deux éléments se superposent, c'est celui qui apparaît en dernier dans le code HTML qui s'affiche par-dessus. C'est la règle de l'ordre du document : le dernier arrivé est au premier plan.
<!-- Sans z-index : .rouge s'affiche PAR-DESSUS .bleu car il est après dans le HTML -->
<div class="bleu">Bleu</div>
<div class="rouge">Rouge</div>
.bleu {
position: absolute;
top: 20px; left: 20px;
width: 100px; height: 100px;
background-color: #2E6DA4;
}
.rouge {
position: absolute;
top: 50px; left: 50px;
width: 100px; height: 100px;
background-color: #C0392B;
/* Rouge est après Bleu dans le HTML => Rouge est au-dessus */
}
La propriété z-index vous permet de contrôler explicitement cet ordre d'empilement. Elle accepte des valeurs entières, positives ou négatives. Plus la valeur est élevée, plus l'élément est au premier plan.
.bleu {
position: absolute;
z-index: 10; /* Bleu passe devant Rouge malgré son ordre dans le HTML */
}
.rouge {
position: absolute;
z-index: 5; /* Rouge est maintenant derrière Bleu */
}
Condition indispensable : z-index ne fonctionne que sur les éléments dont la propriété position est différente de static. Sur un élément static (la valeur par défaut), z-index est simplement ignoré. C'est l'erreur la plus fréquente : écrire z-index: 999 sur un élément sans lui donner de position, et se demander pourquoi rien ne change.
/* z-index ignoré : position est static par défaut */
.element {
z-index: 999; /* Aucun effet */
}
/* z-index actif : l'élément a une position différente de static */
.element {
position: relative; /* ou absolute, fixed, sticky */
z-index: 999; /* Fonctionne */
}
Les valeurs négatives sont également possibles. Elles permettent de placer un élément derrière le flux normal du document, par exemple pour créer un effet de fond décoratif.
.decoration-arriere-plan {
position: absolute;
z-index: -1; /* Passe derrière les éléments du flux normal */
top: 0; left: 0;
width: 100%; height: 100%;
background-color: rgba(46, 109, 164, 0.1);
}
Le concept de contexte d'empilement est plus avancé, mais il est essentiel pour comprendre pourquoi z-index produit parfois des résultats inattendus.
Un contexte d'empilement est une zone indépendante dans laquelle les éléments sont empilés les uns par rapport aux autres. Chaque contexte d'empilement forme une sorte de groupe hermétique : les éléments à l'intérieur ne peuvent jamais passer devant ou derrière des éléments extérieurs à leur contexte, quelle que soit leur valeur de z-index.
Un contexte d'empilement est créé dans plusieurs situations :
/* 1. Un élément positionné avec un z-index autre que auto */
.nouveau-contexte {
position: relative;
z-index: 1; /* Crée un nouveau contexte d'empilement */
}
/* 2. Un élément avec opacity inférieur à 1 */
.nouveau-contexte {
opacity: 0.99; /* Crée un contexte d'empilement, même sans position */
}
/* 3. Un élément avec transform */
.nouveau-contexte {
transform: translateX(0); /* Crée un contexte d'empilement */
}
Voici l'exemple classique qui déroute les développeurs :
<div class="parent-a"> <!-- z-index: 1 => contexte A -->
<div class="enfant-a"> <!-- z-index: 999, mais DANS le contexte A -->
</div>
</div>
<div class="parent-b"> <!-- z-index: 2 => contexte B, AU-DESSUS de A -->
<div class="enfant-b"> <!-- z-index: 1, mais dans le contexte B -->
</div>
</div>
.parent-a {
position: relative;
z-index: 1; /* Contexte A : z-index 1 par rapport au document */
}
.enfant-a {
position: relative;
z-index: 999;
/* Malgré son z-index énorme, .enfant-a reste dans le contexte A.
Il ne peut jamais passer devant .parent-b ni .enfant-b,
car le contexte A (z-index: 1) est sous le contexte B (z-index: 2). */
}
.parent-b {
position: relative;
z-index: 2; /* Contexte B est au-dessus du contexte A */
}
.enfant-b {
position: relative;
z-index: 1;
/* .enfant-b passe devant .enfant-a malgré son z-index de 1 vs 999,
car le contexte B (z-index: 2) est au-dessus du contexte A (z-index: 1). */
}
À retenir : le z-index d'un élément n'est comparé qu'avec les éléments du même contexte d'empilement. Pour comparer des éléments de contextes différents, c'est le z-index de leurs contextes respectifs qui compte, pas le leur propre.
En pratique, pour éviter les surprises avec les contextes d'empilement, il vaut mieux :
z-index espacées (10, 20, 30...) plutôt que consécutives (1, 2, 3), pour pouvoir en insérer facilement une entre deux.opacity et transform sur des conteneurs).Objectif : construire une carte d'article avec une image (simulée par un bloc coloré) sur laquelle apparaît un overlay semi-transparent au survol, avec un texte centré par-dessus. L'exercice travaille position: absolute pour l'overlay, z-index pour contrôler qui passe devant qui, et :hover pour déclencher l'apparition.
Consignes HTML :
overlay.html.<div> avec la classe carte-article. À l'intérieur :
<div> avec la classe image-wrapper contenant :
<div> avec la classe image (simulera l'image).<div> avec la classe overlay contenant un <p> avec le texte Lire l'article.<h2> avec un titre d'article.<p> avec la classe resume et un court texte.overlay.css.Consignes CSS :
.carte-article est centrée (max-width: 360px, margin: 60px auto), avec fond blanc et ombre..image-wrapper a une position relative et overflow: hidden. Il a une hauteur de 220px..image remplit entièrement son parent (toute la largeur, toute la hauteur) avec un fond coloré..overlay est en position absolute, couvre entièrement l'.image-wrapper (top: 0, left: 0, width: 100%, height: 100%). Son fond est rgba(0, 0, 0, 0.55). Il est masqué par défaut avec une opacité de 0 et une transition.Lire l'article dans l'overlay doit être centré horizontalement et verticalement (utilisez text-align: center et line-height égale à la hauteur de l'overlay). Il est blanc et en gras..carte-article, l'overlay doit apparaître (opacity: 1).<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="overlay.css">
<title>Carte avec overlay</title>
</head>
<body>
<div class="carte-article">
<div class="image-wrapper">
<div class="image"></div>
<div class="overlay">
<p>Lire l'article</p>
</div>
</div>
<h2>Les secrets du positionnement CSS</h2>
<p class="resume">Découvrez comment maîtriser position, z-index et les contextes d'empilement pour créer des interfaces précises et robustes.</p>
</div>
</body>
</html>
/* overlay.css — Correction exercice 8 */
body {
margin: 0;
background-color: #f0f4f8;
font-family: Arial, sans-serif;
}
.carte-article {
max-width: 360px;
margin: 60px auto;
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
overflow: hidden;
}
/*
position: relative transforme .image-wrapper en point de référence
pour l'overlay en position: absolute.
overflow: hidden s'assure que l'overlay ne déborde pas
en dehors des coins arrondis de la carte.
*/
.image-wrapper {
position: relative;
height: 220px;
overflow: hidden;
}
/* L'image remplit tout le wrapper */
.image {
width: 100%;
height: 100%;
background-color: #a8c4e0;
}
/*
L'overlay est en position: absolute : il sort du flux et se
positionne par rapport à .image-wrapper (son parent positionné).
top/left/width/height à 100% le font couvrir entièrement l'image.
opacity: 0 le rend invisible par défaut sans le retirer du flux.
La transition rend l'apparition progressive et fluide.
*/
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.55);
opacity: 0;
transition: opacity 0.3s ease;
}
.overlay p {
color: #ffffff;
font-weight: bold;
font-size: 18px;
text-align: center;
/* line-height égale à la hauteur du parent pour centrer verticalement */
line-height: 220px;
margin: 0;
letter-spacing: 1px;
text-transform: uppercase;
}
/*
Au survol de la carte entière, l'overlay devient visible.
On cible .carte-article:hover pour que le survol sur le texte
sous l'image ne fasse pas disparaître l'overlay.
*/
.carte-article:hover .overlay {
opacity: 1;
}
.carte-article h2 {
padding: 16px 20px 6px;
font-size: 17px;
color: #1A3A5C;
margin: 0;
}
.resume {
padding: 0 20px 20px;
font-size: 13px;
color: #666666;
line-height: 1.6;
margin: 0;
}
position: relative sur .image-wrapper et position: absolute sur .overlay : c'est à nouveau le pattern fondamental. L'overlay doit se superposer précisément à l'image — pas à la carte entière, pas à la page. En mettant position: relative sur .image-wrapper, on garantit que l'overlay absolu se positionne par rapport à lui, et uniquement lui.
opacity: 0 pour masquer plutôt que display: none : si l'on utilisait display: none pour masquer l'overlay et display: block pour l'afficher, la propriété transition n'aurait aucun effet — on ne peut pas faire de transition sur display. opacity, en revanche, est une propriété numérique qui se prête parfaitement aux transitions fluides.
.carte-article:hover .overlay : cette règle CSS signifie "l'overlay qui se trouve à l'intérieur d'une .carte-article survolée". En ciblant le survol sur le conteneur parent plutôt que sur l'overlay lui-même, on évite l'effet de scintillement qui apparaîtrait si l'on ciblait .overlay:hover — l'overlay apparaîtrait et disparaîtrait en boucle au passage de la souris.
Erreur fréquente : oublier position: relative sur .image-wrapper. L'overlay remonterait alors jusqu'au premier ancêtre positionné, voire jusqu'au <body>, et couvrirait toute la page au lieu de l'image uniquement.
Objectif : créer une composition graphique de trois calques colorés qui se superposent, et contrôler leur ordre d'affichage exclusivement avec z-index. L'exercice est volontairement ciblé sur un seul concept : observer et manipuler l'ordre d'empilement, et comprendre pourquoi z-index ne fonctionne que sur les éléments positionnés.
Consignes HTML :
calques.html.<div> avec la classe scene.<div> avec respectivement les classes calque calque-bleu, calque calque-rouge, calque calque-jaune. Chaque <div> contient un <span> avec son nom : Calque Bleu, Calque Rouge, Calque Jaune.calques.css.Consignes CSS :
.scene a position: relative, une largeur de 400px, une hauteur de 300px et un fond gris clair. Elle est centrée sur la page..calque sont en position: absolute, mesurent 200px par 200px et ont un border-radius: 8px et un padding: 12px..calque-bleu : top: 20px, left: 20px
.calque-rouge : top: 60px, left: 80px
.calque-jaune : top: 100px, left: 140px
z-index, observez l'ordre d'affichage par défaut (le dernier dans le HTML est au premier plan).z-index pour que l'ordre soit : Bleu au-dessus, puis Jaune, puis Rouge en dessous. Ce résultat va à l'encontre de l'ordre du HTML — c'est l'objectif.z-index: 999 sur .calque-rouge sans lui donner de position et observez que rien ne change.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="calques.css">
<title>Calques superposés</title>
</head>
<body>
<div class="scene">
<div class="calque calque-bleu">
<span>Calque Bleu</span>
</div>
<div class="calque calque-rouge">
<span>Calque Rouge</span>
</div>
<div class="calque calque-jaune">
<span>Calque Jaune</span>
</div>
</div>
</body>
</html>
/* calques.css — Correction exercice 9 */
body {
margin: 0;
padding: 60px;
font-family: Arial, sans-serif;
background-color: #f0f4f8;
}
.scene {
position: relative; /* Contexte de référence pour les calques absolus */
width: 400px;
height: 300px;
background-color: #e8e8e8;
border: 2px dashed #aaaaaa;
border-radius: 8px;
}
.calque {
position: absolute; /* Tous les calques sortent du flux */
width: 200px;
height: 200px;
border-radius: 8px;
padding: 12px;
font-weight: bold;
font-size: 14px;
color: #ffffff;
}
/* Positionnement des calques pour qu'ils se chevauchent */
.calque-bleu { top: 20px; left: 20px; background-color: #2E6DA4; }
.calque-rouge { top: 60px; left: 80px; background-color: #C0392B; }
.calque-jaune { top: 100px; left: 140px; background-color: #E0A800; color: #333; }
/*
Sans z-index : l'ordre d'affichage suit l'ordre du HTML.
Jaune est au-dessus (dernier dans le HTML), puis Rouge, puis Bleu.
Avec z-index : on inverse cet ordre.
Bleu au-dessus (z-index le plus élevé), puis Jaune, puis Rouge.
Note : z-index fonctionne car tous les .calque ont position: absolute.
Sur un élément static, z-index serait totalement ignoré.
*/
.calque-bleu { z-index: 3; } /* Premier plan */
.calque-jaune { z-index: 2; } /* Deuxième plan */
.calque-rouge { z-index: 1; } /* Arrière-plan */
L'ordre par défaut sans z-index : avant d'ajouter les z-index, l'ordre d'empilement suit strictement l'ordre du HTML. Jaune, étant le dernier <div> dans le code, est au premier plan. C'est la règle de base : le dernier arrivé gagne.
z-index renverse l'ordre : en donnant à Bleu le z-index le plus élevé, il passe devant Rouge et Jaune, même s'il apparaît en premier dans le HTML. C'est exactement le pouvoir de z-index : découpler l'ordre visuel de l'ordre du code.
z-index sans position = aucun effet : si vous retirez position: absolute de .calque-rouge et que vous lui mettez z-index: 999, rien ne change. Le navigateur ignore complètement le z-index sur les éléments statiques. C'est la règle absolue à mémoriser.
Valeurs espacées : notez que les z-index utilisés sont 1, 2, 3. En production, on préférera 10, 20, 30 ou même 100, 200, 300 — pour pouvoir facilement insérer une nouvelle valeur entre deux existantes sans avoir à tout renuméroter.
Avant l'arrivée de Flexbox et CSS Grid, les développeurs web n'avaient pas de véritable outil natif pour créer des mises en page en colonnes. La propriété float a été détournée de son usage original pour remplir ce rôle pendant plus d'une décennie. Aujourd'hui, float est revenu à sa vocation première, mais vous la croiserez inévitablement dans du code existant. Il est donc indispensable de la comprendre en profondeur.
La propriété float a été conçue à l'origine pour un usage typographique : permettre à une image de flotter sur le côté d'un bloc de texte, exactement comme dans la mise en page d'un journal ou d'un magazine. Le texte environnant devait alors se répartir naturellement autour de l'image.
/* Usage original et toujours pertinent de float */
.illustration {
float: left; /* L'image flotte à gauche */
margin-right: 20px; /* Espace entre l'image et le texte */
margin-bottom: 10px;
width: 200px;
}
/*
Le texte contenu dans le même bloc parent va automatiquement
s'enrouler autour de l'image flottante, à sa droite et en dessous.
C'est le comportement natif et voulu de float.
*/
<div class="article">
<img class="illustration" src="photo.jpg" alt="Illustration">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Le texte se répartit naturellement autour de l'image flottante,
exactement comme dans une mise en page de magazine...</p>
</div>
C'est dans les années 2000, face à l'absence d'alternatives, que les développeurs ont commencé à utiliser float pour créer des mises en page entières en colonnes. Cette pratique a fonctionné, mais elle a introduit des comportements complexes et souvent contre-intuitifs que les développeurs ont dû apprendre à gérer à la main. Flexbox (2012) puis CSS Grid (2017) ont mis fin à cette pratique, mais des millions de lignes de code utilisant float pour la mise en page existent encore aujourd'hui.
La propriété float accepte trois valeurs principales : left, right et none (la valeur par défaut).
element { float: left; } /* L'élément flotte à gauche, le contenu s'enroule à droite */
element { float: right; } /* L'élément flotte à droite, le contenu s'enroule à gauche */
element { float: none; } /* Comportement par défaut, annule un float hérité */
Quand un élément reçoit float: left ou float: right, plusieurs choses se produisent simultanément qu'il est crucial de bien comprendre :
Premièrement : l'élément est partiellement retiré du flux normal. Il sort du flux mais reste dans son contexte de formatage. Les éléments blocs qui le suivent se comportent comme s'il n'existait pas et remontent pour occuper l'espace. En revanche, le contenu inline (texte, images inline) de ces blocs s'enroule autour du flottant.
.photo {
float: left;
width: 250px;
margin-right: 20px;
}
<div class="conteneur">
<div class="photo">[ image ]</div>
<p>Ce texte s'enroule autour de la photo flottante.
Le div <p> lui-même remonte comme si la photo n'existait pas,
mais son contenu texte s'enroule autour du flottant.
C'est le comportement hybride caractéristique de float.</p>
</div>
Deuxièmement : l'élément flottant prend la largeur de son contenu (ou la largeur que vous lui définissez explicitement). Contrairement à un bloc normal, il ne s'étire plus sur 100% de la largeur disponible.
Troisièmement : le conteneur parent ne voit plus le flottant pour le calcul de sa hauteur. C'est le problème le plus connu de float : si un conteneur ne contient que des éléments flottants, sa hauteur calculée est zéro. Il s'effondre sur lui-même.
/* Problème classique : le conteneur s'effondre */
.conteneur {
background-color: lightblue;
/* Si tous les enfants sont en float, la hauteur du conteneur est 0.
Le fond bleu ne sera pas visible. */
}
.colonne-gauche {
float: left;
width: 45%;
}
.colonne-droite {
float: right;
width: 45%;
}
Voici un exemple complet d'une mise en page deux colonnes avec float, telle qu'elle était couramment écrite avant Flexbox :
/* Mise en page deux colonnes à l'ancienne */
.conteneur {
width: 900px;
margin: 0 auto;
}
.colonne-principale {
float: left;
width: 620px;
}
.colonne-secondaire {
float: right;
width: 240px;
}
/* Sans clearfix, .conteneur aurait une hauteur de 0 */
/* (voir section 6.6.3 pour la solution) */
La propriété clear est la réponse directe aux problèmes causés par float. Elle indique à un élément qu'il ne doit pas se placer à côté d'un flottant, mais passer en dessous de lui.
element { clear: left; }
/* L'élément passe sous tous les flottants à gauche */
element { clear: right; }
/* L'élément passe sous tous les flottants à droite */
element { clear: both; }
/* L'élément passe sous tous les flottants, quel que soit leur côté */
/* C'est la valeur la plus utilisée en pratique */
element { clear: none; }
/* Valeur par défaut : l'élément ne tient pas compte des flottants */
Le problème du conteneur effondré et ses solutions.
Quand un conteneur ne contient que des éléments flottants, il s'effondre à une hauteur nulle. Plusieurs solutions existent pour forcer le conteneur à englober ses enfants flottants.
Solution 1 : l'élément vide avec clear: both (ancienne méthode, déconseillée).
<div class="conteneur">
<div class="colonne-gauche">...</div>
<div class="colonne-droite">...</div>
<div class="clearfix-element"></div> <!-- Élément vide ajouté -->
</div>
.clearfix-element {
clear: both; /* Force le conteneur à s'étendre sous les flottants */
}
/* Inconvénient : pollue le HTML avec un élément purement décoratif */
Solution 2 : overflow: hidden sur le conteneur (méthode simple).
.conteneur {
overflow: hidden;
/* ou overflow: auto; */
/* Crée un nouveau contexte de formatage de bloc qui englobe les flottants */
}
/* Inconvénient : peut masquer du contenu qui déborde intentionnellement */
Solution 3 : le clearfix avec pseudo-élément (méthode recommandée).
C'est la solution la plus propre et la plus utilisée. Elle consiste à utiliser le pseudo-élément ::after pour insérer automatiquement un élément de dégagement après le conteneur, sans toucher au HTML.
/* La classe clearfix à appliquer sur tout conteneur de flottants */
.clearfix::after {
content: ""; /* Le pseudo-élément doit avoir un contenu, même vide */
display: block; /* Doit être un bloc pour que clear fonctionne */
clear: both; /* Passe sous tous les flottants */
}
<!-- En HTML : on ajoute simplement la classe clearfix au conteneur -->
<div class="conteneur clearfix">
<div class="colonne-gauche">...</div>
<div class="colonne-droite">...</div>
<!-- Plus besoin d'élément vide dans le HTML -->
</div>
Bonne pratique : définissez la classe
.clearfixune seule fois dans votre CSS et appliquez-la à tous les conteneurs de flottants. C'est une règle que l'on retrouve dans quasiment toutes les feuilles de style des projets utilisantfloatpour la mise en page.
Voici un exemple complet et fonctionnel d'une mise en page avec float et clearfix :
/* Exemple complet : float + clearfix */
.conteneur {
width: 860px;
margin: 0 auto;
background-color: #f0f4f8;
padding: 20px;
}
/* Le clearfix empêche le conteneur de s'effondrer */
.conteneur::after {
content: "";
display: block;
clear: both;
}
.principale {
float: left;
width: 580px;
background-color: #ffffff;
padding: 20px;
}
.secondaire {
float: right;
width: 220px;
background-color: #e8f0fe;
padding: 20px;
}
/* Le footer passe sous les deux colonnes flottantes */
.pied-de-page {
clear: both;
background-color: #1A3A5C;
color: white;
padding: 20px;
text-align: center;
}
Objectif : reproduire la mise en page typographique originelle de float : une image qui flotte à gauche d'un article, avec le texte qui s'enroule naturellement autour d'elle. C'est l'usage le plus légitime et le plus simple de float, celui pour lequel il a été conçu.
Consignes HTML :
magazine.html.<article> avec la classe article-magazine.<div> avec la classe photo (simulera la photo de l'article).<h2> avec un titre d'article.<p> avec du texte fictif suffisamment long (au moins 4-5 lignes chacun) pour que le texte s'enroule bien autour de la photo.<div> avec la classe auteur contenant un <span> avec le nom d'un auteur fictif et un <span> avec la classe date contenant une date.magazine.css.Consignes CSS :
article-magazine a un max-width de 720px, est centré, avec un fond blanc, du padding et une ombre légère..photo flotte à gauche, mesure 240px de large et 180px de haut, avec un fond coloré, un border-radius, et une marge à droite et en bas pour l'espacer du texte.<h2> a une couleur sombre et une marge basse réduite.<p> ont une taille de police de 15px et un interligne confortable..auteur doit passer sous la photo et le texte grâce à clear: both. Elle a une bordure haute, du padding et les deux <span> sont stylisés différemment (nom en gras, date en italique et plus clair).<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="magazine.css">
<title>Article de magazine</title>
</head>
<body>
<article class="article-magazine">
<div class="photo"></div>
<h2>La typographie web à l'ère du CSS moderne</h2>
<p>Depuis les débuts du web, la typographie a toujours été au cœur des préoccupations des designers. Les premières pages HTML ne disposaient que d'un nombre très limité de polices système, et la mise en forme était rudimentaire. C'est l'arrivée progressive du CSS qui a permis d'affiner considérablement le contrôle typographique.</p>
<p>Aujourd'hui, grâce aux polices web et aux services comme Google Fonts, les designers disposent de milliers de familles typographiques. Les propriétés CSS permettent de contrôler finement l'interlignage, l'espacement entre les lettres, la casse, la graisse et bien d'autres paramètres. La lisibilité est devenue une discipline à part entière dans la conception d'interfaces.</p>
<p>L'interlignage optimal se situe généralement entre 1.5 et 1.8 fois la taille de la police pour le corps de texte. Une largeur de colonne de 60 à 75 caractères par ligne est considérée comme idéale pour la lecture. Ces règles, issues de la typographie traditionnelle, s'appliquent tout aussi bien sur écran que sur papier.</p>
<div class="auteur">
<span class="nom-auteur">Sophie Marchand</span>
<span class="date">14 novembre 2024</span>
</div>
</article>
</body>
</html>
/* magazine.css — Correction exercice 10 */
body {
margin: 0;
padding: 40px;
background-color: #f5f5f0;
font-family: Georgia, serif;
color: #2c2c2c;
}
.article-magazine {
max-width: 720px;
margin: 0 auto;
background-color: #ffffff;
padding: 40px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}
/*
float: left sort la photo du flux et la colle à gauche.
Le texte (inline) des paragraphes suivants s'enroule autour d'elle.
margin-right et margin-bottom créent l'espace entre la photo et le texte.
*/
.photo {
float: left;
width: 240px;
height: 180px;
background-color: #c8daf0;
border-radius: 4px;
margin-right: 24px;
margin-bottom: 12px;
}
h2 {
font-size: 22px;
color: #1A3A5C;
margin-top: 0;
margin-bottom: 14px;
line-height: 1.3;
}
p {
font-size: 15px;
line-height: 1.8;
margin-top: 0;
margin-bottom: 14px;
}
/*
clear: both force .auteur à se placer sous tous les flottants.
Sans cette propriété, .auteur remonterait à côté de la photo
si elle est plus haute que le texte, ou s'afficherait de manière
imprévue selon la quantité de texte.
border-top crée une séparation visuelle nette.
*/
.auteur {
clear: both;
border-top: 1px solid #e0e0e0;
padding-top: 16px;
margin-top: 8px;
font-size: 13px;
}
.nom-auteur {
font-weight: bold;
color: #1A3A5C;
margin-right: 16px;
}
.date {
font-style: italic;
color: #888888;
}
float: left sur .photo : la photo sort partiellement du flux. Les blocs <p> qui suivent remontent comme si la photo n'existait pas, mais leur contenu texte s'enroule autour d'elle à droite. C'est exactement le comportement typographique original pour lequel float a été créé.
margin-right et margin-bottom sur .photo : sans ces marges, le texte collerait directement contre la photo. Ce sont les marges du flottant lui-même qui créent l'espace autour de lui — pas le padding ou les marges des paragraphes.
clear: both sur .auteur : sans cette propriété, si la photo est plus haute que les trois paragraphes, .auteur remonterait partiellement à côté de la photo. clear: both garantit qu'il se place toujours sous le flottant, quelle que soit la quantité de texte. Essayez de retirer cette propriété et de réduire le texte pour observer l'effet.
Erreur fréquente : mettre float sur la photo mais oublier de lui donner une largeur explicite. Sans width, un flottant prend la largeur de son contenu — ce qui est imprévisible pour un <div> vide ou une vraie image non contrainte.
Objectif : construire une mise en page classique avec une colonne principale et une colonne secondaire côte à côte grâce à float, un pied de page qui passe sous les deux colonnes avec clear, et un conteneur qui ne s'effondre pas grâce au clearfix. C'est la mise en page historique par excellence avec float.
Consignes HTML :
deux-colonnes.html.<header> avec un <h1> et un <p class="slogan">.<div> avec les classes conteneur et clearfix contenant :
<div> avec la classe col-principale contenant un <h2>, deux <p> de texte.<aside> avec la classe col-secondaire contenant un <h3> et une petite liste <ul> de trois liens.<footer> avec un <p> de copyright.deux-colonnes.css.Consignes CSS :
<header> a un fond sombre, du padding et le texte est centré en blanc..conteneur a un max-width de 900px, est centré et a du padding..clearfix::after pour éviter l'effondrement du conteneur..col-principale flotte à gauche et fait 62% de large. Elle a du padding et un fond blanc..col-secondaire flotte à droite et fait 30% de large. Elle a du padding et un fond légèrement coloré.<footer> a clear: both pour passer sous les deux colonnes, un fond sombre et le texte centré en blanc.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="deux-colonnes.css">
<title>Mise en page deux colonnes</title>
</head>
<body>
<header>
<h1>Le Journal du Dev</h1>
<p class="slogan">Toute l'actualité du développement web</p>
</header>
<div class="conteneur clearfix">
<div class="col-principale">
<h2>CSS Grid : la révolution de la mise en page</h2>
<p>Depuis son adoption par l'ensemble des navigateurs modernes, CSS Grid a profondément transformé la manière dont les développeurs conçoivent leurs mises en page. Fini le recours aux flottants détournés ou aux tableaux de présentation — Grid offre un système bidimensionnel puissant et intuitif.</p>
<p>Les colonnes et les lignes se définissent directement sur le conteneur, et les éléments enfants se placent automatiquement ou manuellement dans la grille. La syntaxe, bien que dense au premier abord, devient rapidement naturelle et expressive.</p>
</div>
<aside class="col-secondaire">
<h3>À lire aussi</h3>
<ul>
<li><a href="#">Introduction à Flexbox</a></li>
<li><a href="#">Les media queries en 2024</a></li>
<li><a href="#">Variables CSS natives</a></li>
</ul>
</aside>
</div>
<footer>
<p>© 2024 Le Journal du Dev. Tous droits réservés.</p>
</footer>
</body>
</html>
/* deux-colonnes.css — Correction exercice 11 */
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: Arial, sans-serif;
background-color: #f4f4f4;
color: #2c2c2c;
}
header {
background-color: #1A3A5C;
padding: 24px 30px;
text-align: center;
}
header h1 {
color: #ffffff;
margin: 0 0 6px 0;
font-size: 28px;
}
.slogan {
color: #a8c4e0;
margin: 0;
font-style: italic;
font-size: 14px;
}
.conteneur {
max-width: 900px;
margin: 0 auto;
padding: 24px;
}
/*
Le clearfix empêche .conteneur de s'effondrer à hauteur zéro
quand tous ses enfants sont en float.
::after insère un élément invisible après le contenu du conteneur,
lequel force le conteneur à s'étendre sous ses enfants flottants.
*/
.clearfix::after {
content: "";
display: block;
clear: both;
}
/*
.col-principale flotte à gauche et occupe 62% de la largeur.
box-sizing: border-box garantit que le padding est inclus dans ce %.
*/
.col-principale {
float: left;
width: 62%;
background-color: #ffffff;
padding: 24px;
border: 1px solid #e0e0e0;
}
.col-principale h2 {
color: #1A3A5C;
margin-top: 0;
font-size: 20px;
}
.col-principale p {
line-height: 1.8;
font-size: 15px;
color: #444444;
}
/*
.col-secondaire flotte à droite et occupe 30%.
Les 8% restants (100 - 62 - 30) servent de gouttière entre les colonnes.
*/
.col-secondaire {
float: right;
width: 30%;
background-color: #e8f0fe;
padding: 20px;
border: 1px solid #c8daf0;
}
.col-secondaire h3 {
color: #1A3A5C;
margin-top: 0;
font-size: 16px;
border-bottom: 2px solid #2E6DA4;
padding-bottom: 8px;
}
.col-secondaire ul {
list-style: none;
padding: 0;
margin: 0;
}
.col-secondaire li {
padding: 8px 0;
border-bottom: 1px solid #c8daf0;
}
.col-secondaire li:last-child {
border-bottom: none;
}
.col-secondaire a {
color: #2E6DA4;
text-decoration: none;
font-size: 14px;
}
.col-secondaire a:hover {
text-decoration: underline;
}
/*
clear: both sur le footer le force à se placer sous les deux colonnes
flottantes, quelle que soit leur hauteur respective.
Sans clear: both, le footer remonterait à côté des colonnes.
*/
footer {
clear: both;
background-color: #1A3A5C;
color: #ffffff;
text-align: center;
padding: 20px 30px;
font-size: 13px;
}
La gouttière entre les colonnes : les deux colonnes font 62% + 30% = 92% de large. Les 8% restants forment naturellement l'espace entre elles. C'est une technique courante avec les flottants : on répartit les pourcentages de sorte que leur somme soit inférieure à 100%, et le reste crée la gouttière automatiquement.
box-sizing: border-box est indispensable : sans lui, le padding: 24px de .col-principale s'ajoute à ses 62% de largeur. La somme dépasse alors 100% et la colonne secondaire tombe à la ligne. border-box inclut le padding dans la largeur déclarée, ce qui rend les calculs de pourcentages fiables.
.clearfix::after sur le conteneur : sans le clearfix, .conteneur a une hauteur calculée de zéro car tous ses enfants sont des flottants. Son fond et sa bordure éventuels seraient invisibles, et footer remonterait directement sous header. Le clearfix force le conteneur à "voir" ses enfants flottants pour calculer sa hauteur.
clear: both sur footer : même avec le clearfix sur le conteneur, le footer est un élément extérieur au conteneur. Il faut lui indiquer explicitement de passer sous tous les flottants. Sans clear: both, il remonterait à côté des colonnes si sa largeur le permet.
Pourquoi ne plus utiliser float pour la mise en page ? Flexbox résout ces problèmes en une seule propriété, sans clearfix, sans calculs de pourcentages manuels, et avec un contrôle bien supérieur de l'alignement. float reste pertinent uniquement pour l'enroulement de texte autour d'images, son usage original.
Le centrage est l'une des tâches les plus fréquentes en CSS, et paradoxalement l'une de celles qui a longtemps posé le plus de difficultés. Il n'existe pas une seule méthode universelle : la bonne technique dépend du type d'élément à centrer, de son contexte, et de si le centrage est horizontal, vertical, ou les deux. Cette section vous donne les outils pour répondre à chaque situation.
Le centrage horizontal est le plus simple à obtenir, mais il existe plusieurs façons de le faire selon la nature de l'élément.
Centrer un élément bloc.
Un élément bloc dont on a défini une largeur peut être centré horizontalement en lui appliquant margin: 0 auto. Les marges gauche et droite automatiques se partagent équitablement l'espace restant disponible de part et d'autre de l'élément.
/* margin: 0 auto fonctionne uniquement si :
1. L'élément est un bloc (display: block)
2. L'élément a une largeur définie (width ou max-width)
Sans largeur explicite, le bloc prend 100% de son parent
et il n'y a pas d'espace à partager — le centrage n'a aucun effet. */
.conteneur {
width: 800px; /* ou max-width: 800px */
margin: 0 auto; /* Gauche et droite automatiques = centrage */
}
C'est la technique la plus utilisée pour centrer le layout principal d'une page, les articles, les formulaires, et tous les blocs de contenu dont on veut limiter la largeur.
/* Usage typique : centrer le contenu principal d'une page */
.page {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px; /* Respiration sur les petits écrans */
}
Centrer du contenu inline ou inline-block.
Pour centrer du texte, des images, des liens ou des éléments inline-block à l'intérieur de leur conteneur, on utilise text-align: center sur le parent. Cette propriété s'applique à tout le contenu inline du bloc.
/* text-align: center sur le parent centre tout son contenu inline */
.banniere {
text-align: center;
background-color: #1A3A5C;
padding: 40px;
}
/* Le h1 et le p (dont le texte est inline) seront centrés */
/* Les éléments inline-block dans .banniere seront aussi centrés */
/* Centrer des boutons inline-block avec text-align sur le parent */
.groupe-boutons {
text-align: center;
}
.bouton {
display: inline-block;
padding: 10px 24px;
background-color: #2E6DA4;
color: white;
}
/* Les boutons se centrent dans .groupe-boutons grâce au text-align du parent */
Distinction importante :
text-align: centercentre le contenu à l'intérieur d'un élément.margin: 0 autocentre l'élément lui-même dans son parent. Ces deux propriétés ne sont pas interchangeables.
Centrer un élément inline-block dans un bloc.
Combinez les deux techniques : text-align: center sur le parent pour centrer l'inline-block, et si nécessaire text-align: left sur l'inline-block lui-même pour que son propre contenu texte ne soit pas centré.
.parent {
text-align: center; /* Centre les enfants inline-block */
}
.enfant {
display: inline-block;
width: 300px;
text-align: left; /* Le texte à l'intérieur reste aligné à gauche */
}
Le centrage vertical est historiquement beaucoup plus difficile à obtenir en CSS que le centrage horizontal. Contrairement à margin: 0 auto, il n'existe pas de propriété margin: auto 0 qui fonctionnerait pour les marges verticales dans le flux normal. Plusieurs techniques existent, chacune avec ses conditions et ses limites.
Technique 1 : line-height égale à la hauteur du conteneur.
Cette technique fonctionne uniquement pour une seule ligne de texte dans un conteneur à hauteur fixe. Elle est très utilisée pour les boutons, les cellules de navigation, et les labels d'une ligne.
.bouton {
height: 48px;
line-height: 48px; /* line-height = height => texte centré verticalement */
padding: 0 20px;
text-align: center;
}
/* Limites : ne fonctionne que pour une seule ligne de texte.
Si le texte passe sur deux lignes, le résultat est cassé. */
Technique 2 : padding symétrique.
Plutôt que de fixer une hauteur et de jouer sur line-height, on peut laisser le contenu définir la hauteur de l'élément et appliquer un padding égal en haut et en bas. Le contenu se retrouve naturellement centré verticalement.
.carte {
padding: 40px 30px; /* 40px en haut et en bas = centrage visuel vertical */
/* Pas besoin de définir une hauteur : l'élément s'adapte au contenu */
}
/* Avantage : s'adapte à n'importe quelle quantité de contenu.
Inconvénient : ne centre pas dans un conteneur à hauteur fixe. */
Technique 3 : position: absolute avec top: 50% et transform.
Pour centrer un élément dans un conteneur à hauteur fixe, on peut combiner le positionnement absolu avec transform. C'est la technique classique pré-Flexbox pour un centrage précis.
.conteneur {
position: relative;
height: 300px;
}
.element-centre {
position: absolute;
top: 50%; /* Place le bord supérieur au milieu */
left: 50%; /* Place le bord gauche au milieu */
transform: translate(-50%, -50%); /* Décale de la moitié de sa propre taille */
}
/* Explication du translate :
top: 50% positionne le COIN SUPÉRIEUR GAUCHE de l'élément au centre.
L'élément dépasse donc vers le bas et la droite.
transform: translate(-50%, -50%) le décale vers le haut et la gauche
de la moitié de sa propre largeur et hauteur.
Résultat : le CENTRE de l'élément coïncide avec le centre du conteneur. */
Technique 4 : display: table-cell avec vertical-align: middle.
Une technique ancienne mais qui fonctionne dans des contextes spécifiques, notamment pour centrer verticalement dans un conteneur à hauteur fixe sans avoir recours au positionnement absolu.
.conteneur {
display: table-cell; /* Se comporte comme une cellule de tableau */
vertical-align: middle; /* Centrage vertical natif des cellules de tableau */
height: 200px;
width: 400px;
}
Remarque : Flexbox et CSS Grid offrent des solutions bien plus simples et puissantes pour le centrage vertical. Ces techniques restent importantes à connaître pour comprendre du code existant et pour les rares cas où Flexbox n'est pas applicable.
Tableau de synthèse des techniques de centrage :
SituationTechnique recommandée
|
Centrer un bloc (div, article...) |
|
|
Centrer du texte ou des inline-block |
|
|
Une ligne de texte dans un bouton |
|
|
Centrage vertical avec padding |
|
|
Centrage horizontal + vertical précis |
|
Objectif : construire une page d'accueil composée d'un héros centré (titre + sous-titre + bouton), d'une section de trois blocs côte à côte avec leur contenu centré, et d'un pied de page. L'exercice travaille les différentes techniques de centrage horizontal dans un contexte réaliste.
Consignes HTML :
accueil.html.<section> avec la classe hero contenant un <h1>, un <p> de sous-titre et un <a> avec la classe cta.<section> avec la classe fonctionnalites contenant trois <div> avec la classe bloc. Chaque bloc contient un <div> avec la classe icone (une initiale ou un symbole), un <h3> et un <p> de description.<footer> avec un <p> de copyright.accueil.css.Consignes :
.hero occupe toute la largeur, a un fond coloré sombre. Son contenu (titre, sous-titre, bouton) doit être centré horizontalement. Le bouton .cta doit lui aussi être centré et avoir l'apparence d'un bouton..fonctionnalites doit avoir un contenu limité en largeur et centré sur la page. Les trois .bloc doivent s'afficher côte à côte et leur contenu interne doit être centré..icone de chaque bloc doit être centrée dans le bloc, avec une taille et un fond visibles.<footer> a un fond sombre avec son texte centré.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="accueil.css">
<title>Accueil</title>
</head>
<body>
<section class="hero">
<h1>Apprenez le CSS une bonne fois pour toutes</h1>
<p class="sous-titre">Des formations claires, progressives et immédiatement applicables.</p>
<a href="#" class="cta">Commencer maintenant</a>
</section>
<section class="fonctionnalites">
<div class="bloc">
<div class="icone">C</div>
<h3>Cours structurés</h3>
<p>Un parcours progressif du flux normal jusqu'aux techniques avancées de mise en page.</p>
</div>
<div class="bloc">
<div class="icone">E</div>
<h3>Exercices pratiques</h3>
<p>Des exercices concrets après chaque notion pour ancrer les apprentissages durablement.</p>
</div>
<div class="bloc">
<div class="icone">P</div>
<h3>Projets réels</h3>
<p>Des projets complets en fin de module pour mettre en pratique l'ensemble des notions.</p>
</div>
</section>
<footer>
<p>© 2024 FormationCSS. Tous droits réservés.</p>
</footer>
</body>
</html>
/* accueil.css — Correction exercice 12 */
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: Arial, sans-serif;
color: #2c2c2c;
}
/* --- HERO --- */
.hero {
background-color: #1A3A5C;
padding: 80px 20px;
/* text-align: center centre tout le contenu inline du hero :
le h1, le p (leur texte) et le lien .cta (inline par défaut) */
text-align: center;
}
.hero h1 {
color: #ffffff;
font-size: 32px;
margin: 0 0 16px 0;
/* max-width + margin: 0 auto limitent et centrent le titre
pour qu'il ne soit pas trop large sur grand écran */
max-width: 640px;
margin-left: auto;
margin-right: auto;
margin-bottom: 16px;
}
.sous-titre {
color: #a8c4e0;
font-size: 17px;
margin: 0 0 32px 0;
}
/* .cta est un <a> : inline par défaut.
display: inline-block lui permet d'avoir padding et border-radius
tout en restant centré par le text-align: center du parent. */
.cta {
display: inline-block;
background-color: #2E6DA4;
color: #ffffff;
padding: 14px 32px;
text-decoration: none;
font-weight: bold;
font-size: 16px;
border-radius: 4px;
transition: background-color 0.2s ease;
}
.cta:hover {
background-color: #3a84c4;
}
/* --- FONCTIONNALITES --- */
.fonctionnalites {
/* max-width + margin: 0 auto centre le bloc de fonctionnalités.
C'est la technique de centrage d'un élément bloc. */
max-width: 900px;
margin: 0 auto;
padding: 60px 20px;
/* font-size: 0 supprime les espaces blancs entre les .bloc inline-block */
font-size: 0;
text-align: center;
}
/* Chaque .bloc est inline-block pour être côte à côte.
text-align: center centre leur contenu interne. */
.bloc {
display: inline-block;
vertical-align: top;
width: 30%;
margin: 0 1.5%;
padding: 30px 20px;
background-color: #f9f9f9;
border: 1px solid #e8e8e8;
border-radius: 8px;
text-align: center; /* Centre le contenu à l'intérieur de chaque bloc */
font-size: 15px; /* Restaure la font-size annulée sur .fonctionnalites */
}
/* .icone est un <div> : bloc par défaut.
margin: 0 auto le centre horizontalement dans .bloc.
Il a une largeur fixe pour ne pas prendre toute la largeur. */
.icone {
width: 60px;
height: 60px;
background-color: #2E6DA4;
color: #ffffff;
font-size: 26px;
font-weight: bold;
border-radius: 50%;
/* line-height = height pour centrer verticalement le texte sur une ligne */
line-height: 60px;
text-align: center;
margin: 0 auto 20px auto; /* margin auto gauche+droite = centrage horizontal du bloc */
}
.bloc h3 {
color: #1A3A5C;
font-size: 17px;
margin: 0 0 10px 0;
}
.bloc p {
color: #666666;
font-size: 14px;
line-height: 1.7;
margin: 0;
}
/* --- FOOTER --- */
footer {
background-color: #1A3A5C;
color: #a8c4e0;
text-align: center;
padding: 24px;
font-size: 13px;
}
footer p {
margin: 0;
}
text-align: center sur .hero : cette propriété s'applique au contenu inline du bloc, ce qui inclut le texte de h1, le texte de .sous-titre, et le lien .cta (qui est inline). En une seule déclaration sur le parent, tout le contenu se centre. C'est la technique pour centrer des éléments inline et inline-block.
margin: 0 auto sur .hero h1 : le titre a un max-width: 640px pour ne pas s'étirer sur toute la largeur sur les grands écrans. margin: 0 auto le centre horizontalement dans le hero. Ici on voit les deux techniques coexister : text-align: center pour le texte et les inline-block, margin: 0 auto pour le bloc lui-même.
display: inline-block sur .cta : le lien est inline par défaut. Sans changer son display, padding et border-radius ne s'appliqueraient pas correctement. inline-block lui donne l'apparence d'un bouton tout en restant centré par le text-align: center du parent.
margin: 0 auto sur .icone : l'icône est un <div> (bloc) avec une largeur fixe de 60px. Pour la centrer horizontalement dans .bloc, on lui applique margin: 0 auto. Le text-align: center du parent ne suffit pas ici car .icone est un bloc — text-align ne centre que les éléments inline.
Erreur fréquente : appliquer text-align: center sur un élément bloc en espérant le centrer dans son parent. text-align centre le contenu à l'intérieur, pas l'élément lui-même. Pour centrer un bloc, c'est margin: 0 auto qu'il faut utiliser.
Objectif : construire une carte de profil parfaitement centrée sur la page, horizontalement et verticalement. L'avatar, le nom et les informations doivent être centrés à l'intérieur de la carte. L'exercice travaille la combinaison des techniques de centrage vertical et horizontal dans un cas concret.
Consignes HTML :
profil.html.<div> avec la classe page (représente toute la page).<div> avec la classe carte-profil contenant :
<div> avec la classe avatar (simulera la photo de profil).<h2> avec un nom.<p> avec la classe role (ex : Développeuse Front-End).<p> avec la classe bio (une phrase de description).<a> avec la classe btn-contact et le texte Me contacter.profil.css.Consignes :
.page occupe toute la hauteur de la fenêtre du navigateur. La carte doit être centrée horizontalement et verticalement dans cette page. Trouvez la technique adaptée parmi celles vues dans ce module..carte-profil a une largeur fixe, un fond blanc, du padding et une ombre. Tout son contenu interne est centré horizontalement..avatar est un cercle centré, avec un fond coloré et une taille de 100px par 100px..btn-contact ressemble à un bouton, centré dans la carte.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="profil.css">
<title>Carte de profil</title>
</head>
<body>
<div class="page">
<div class="carte-profil">
<div class="avatar"></div>
<h2>Thomas Leroy</h2>
<p class="role">Développeur Front-End</p>
<p class="bio">Passionné par le CSS et l'accessibilité web, je crée des interfaces soignées et performantes.</p>
<a href="#" class="btn-contact">Me contacter</a>
</div>
</div>
</body>
</html>
/* profil.css — Correction exercice 13 */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
}
/* La page doit occuper toute la hauteur de la fenêtre.
height: 100vh = 100% de la hauteur du viewport.
position: relative est nécessaire pour que le centrage
absolu de la carte fonctionne par rapport à elle. */
.page {
position: relative;
height: 100vh;
background-color: #f0f4f8;
}
/* Centrage horizontal + vertical avec position absolute et transform.
C'est la technique classique pré-Flexbox pour un centrage précis
dans un conteneur à hauteur fixe (ici 100vh). */
.carte-profil {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 320px;
background-color: #ffffff;
border-radius: 12px;
padding: 40px 30px;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
/* text-align: center centre tout le contenu inline de la carte :
textes, et les éléments inline-block comme .btn-contact */
text-align: center;
}
/* L'avatar est un <div> (bloc) avec une largeur fixe.
margin: 0 auto le centre horizontalement dans la carte.
border-radius: 50% le transforme en cercle. */
.avatar {
width: 100px;
height: 100px;
background-color: #2E6DA4;
border-radius: 50%;
margin: 0 auto 20px auto;
}
.carte-profil h2 {
font-size: 20px;
color: #1A3A5C;
margin-bottom: 6px;
}
.role {
color: #2E6DA4;
font-size: 14px;
font-weight: bold;
margin-bottom: 16px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.bio {
font-size: 13px;
color: #666666;
line-height: 1.7;
margin-bottom: 24px;
}
/* .btn-contact est un <a> : inline par défaut.
display: inline-block lui donne l'apparence d'un bouton.
Il est centré par le text-align: center de .carte-profil. */
.btn-contact {
display: inline-block;
background-color: #1A3A5C;
color: #ffffff;
padding: 10px 28px;
text-decoration: none;
border-radius: 4px;
font-size: 14px;
font-weight: bold;
transition: background-color 0.2s ease;
}
.btn-contact:hover {
background-color: #2E6DA4;
}
position: absolute + transform: translate(-50%, -50%) : c'est la technique de centrage précis dans un conteneur à hauteur connue. top: 50% et left: 50% placent le coin supérieur gauche de la carte au centre de la page. Mais l'élément dépasse alors vers le bas et la droite. transform: translate(-50%, -50%) le décale vers le haut et la gauche de la moitié de sa propre taille. Au final, le centre de la carte coïncide avec le centre de la page.
height: 100vh sur .page : vh signifie "viewport height" — 1vh équivaut à 1% de la hauteur de la fenêtre du navigateur. Sans cette déclaration, .page n'aurait que la hauteur de son contenu (la carte), et le centrage vertical ne fonctionnerait pas : il n'y aurait pas d'espace vertical dans lequel centrer.
Deux techniques de centrage horizontal coexistent : text-align: center sur .carte-profil centre son contenu inline (textes, .btn-contact). margin: 0 auto sur .avatar centre l'avatar lui-même dans la carte, car c'est un élément bloc avec une largeur fixe. Ces deux techniques ne sont pas interchangeables et s'appliquent à des situations différentes.
Erreur fréquente : oublier position: relative sur .page. Sans cette déclaration, la carte se positionne par rapport au <body> ou au premier ancêtre positionné — généralement le même résultat dans cet exercice, mais dans une vraie page avec d'autres conteneurs, le résultat pourrait être totalement inattendu.
Flexbox — dont le nom complet est Flexible Box Layout Module — est une technologie de mise en page CSS introduite pour répondre à des limitations fondamentales du modèle de boîte traditionnel. Avant d'apprendre à l'utiliser, il est essentiel de comprendre pourquoi elle a été créée et quels problèmes concrets elle résout. Cela vous permettra de saisir sa logique profonde plutôt que d'en mémoriser les propriétés de manière mécanique.
Pendant toute la première décennie du web moderne, les développeurs ont dû composer avec des outils de mise en page qui n'avaient pas été conçus pour cet usage. float avait été imaginé pour enrouler du texte autour d'images. display: inline-block était un hybride utile mais imprécis. Les tableaux HTML étaient détournés pour créer des grilles. Chacune de ces approches fonctionnait, mais toutes généraient des problèmes récurrents que vous avez déjà rencontrés dans les chapitres précédents.
Problème 1 : aligner des éléments verticalement dans un conteneur.
C'était l'un des problèmes les plus frustrants du CSS classique. Centrer verticalement un élément dans un conteneur dont la hauteur n'était pas connue à l'avance nécessitait des acrobaties : position: absolute combiné à transform: translate, ou display: table-cell avec vertical-align: middle. Aucune de ces solutions n'était intuitive ni robuste.
/* Avant Flexbox : centrage vertical laborieux */
.conteneur {
position: relative;
height: 300px;
}
.element {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
/* Fonctionne, mais sort l'élément du flux et nécessite
de connaître le contexte de positionnement exact */
}
/* Avec Flexbox : centrage vertical en trois propriétés */
.conteneur {
display: flex;
justify-content: center;
align-items: center;
height: 300px;
}
/* L'élément enfant est centré horizontalement et verticalement.
Il reste dans le flux. Aucune connaissance de sa taille n'est requise. */
Problème 2 : distribuer l'espace disponible entre des éléments.
Avec float ou inline-block, répartir des éléments uniformément dans un conteneur demandait des calculs manuels de pourcentages, des gouttières gérées à la main, et un clearfix pour éviter l'effondrement du conteneur.
/* Avant Flexbox : trois colonnes égales avec float */
.colonne {
float: left;
width: 33.333%; /* Calcul manuel */
padding: 0 10px; /* Gouttière manuelle */
box-sizing: border-box;
}
/* Il faut aussi gérer le clearfix sur le parent,
et si le nombre de colonnes change, recalculer tout */
/* Avec Flexbox : trois colonnes égales sans calcul */
.conteneur {
display: flex;
gap: 20px; /* Gouttière gérée automatiquement */
}
.colonne {
flex: 1; /* Chaque colonne prend une part égale de l'espace disponible */
/* Si on ajoute une quatrième colonne, les quatre se partagent l'espace
automatiquement, sans toucher aux autres règles */
}
Problème 3 : faire en sorte que des éléments aient tous la même hauteur.
Avec float, chaque colonne avait sa propre hauteur déterminée par son contenu. Si la colonne de gauche avait plus de texte que celle de droite, elles n'étaient pas de la même hauteur, ce qui cassait l'harmonie visuelle. On utilisait alors des hacks comme des min-height fixes ou du JavaScript pour équilibrer les hauteurs.
/* Avant Flexbox : colonnes inégales, problème courant */
.col-gauche {
float: left;
width: 50%;
/* Hauteur déterminée par le contenu : peut être 200px ou 500px */
}
.col-droite {
float: right;
width: 50%;
/* Hauteur différente si le contenu est plus court ou plus long */
}
/* Avec Flexbox : hauteur égale automatique */
.conteneur {
display: flex;
}
.col-gauche,
.col-droite {
flex: 1;
/* Les deux colonnes ont automatiquement la même hauteur :
celle de la plus haute des deux. Aucun calcul, aucun JS. */
}
Problème 4 : inverser ou réordonner visuellement des éléments sans toucher au HTML. Avant Flexbox, l'ordre visuel des éléments était strictement lié à leur ordre dans le code HTML. Changer l'ordre d'affichage impliquait de modifier le HTML, ce qui pouvait poser des problèmes d'accessibilité ou de logique de code.
/* Avec Flexbox : réordonner visuellement sans toucher au HTML */
.conteneur {
display: flex;
flex-direction: row-reverse; /* Inverse l'ordre d'affichage */
}
/* Ou en ciblant un élément spécifique */
.element-en-premier {
order: -1; /* S'affiche avant tous les autres, quelle que soit sa position dans le HTML */
}
Flexbox excelle dans certains contextes précis. Connaître ces cas d'usage vous aidera à identifier immédiatement quand y recourir plutôt qu'à une autre technique.
Navigation horizontale. C'est sans doute l'usage le plus répandu de Flexbox. Une liste de liens à distribuer dans une barre de navigation, avec parfois un logo à gauche et des liens à droite, ou des liens répartis équitablement sur toute la largeur.
.navbar {
display: flex;
justify-content: space-between; /* Logo à gauche, liens à droite */
align-items: center; /* Alignement vertical centré */
padding: 0 30px;
background-color: #1A3A5C;
height: 60px;
}
Centrage d'un élément dans une zone. Que ce soit une carte centrée dans une page, une icône centrée dans un bouton, ou un texte centré dans une bannière — Flexbox est la solution la plus propre et la plus lisible.
.hero {
display: flex;
justify-content: center;
align-items: center;
height: 400px;
}
/* L'enfant unique du hero sera centré horizontalement et verticalement */
Mise en page en colonnes de même hauteur. Des cartes de contenu, des blocs de fonctionnalités, des colonnes d'article — tous ces éléments bénéficient de l'égalisation automatique des hauteurs que Flexbox offre sans effort supplémentaire.
.grille-cartes {
display: flex;
gap: 20px;
}
.carte {
flex: 1; /* Toutes les cartes ont la même largeur ET la même hauteur */
padding: 20px;
background-color: #ffffff;
border: 1px solid #e0e0e0;
}
Alignement d'un groupe d'éléments avec espacement automatique. Placer un bouton tout à droite d'une barre d'outils, aligner des icônes dans un footer, séparer des éléments de formulaire — Flexbox gère l'espace entre les éléments de manière déclarative.
.barre-outils {
display: flex;
align-items: center;
gap: 12px;
}
.barre-outils .action-principale {
margin-left: auto; /* Pousse cet élément et tout ce qui suit vers la droite */
}
Formulaires et groupes d'inputs. Aligner un label à côté d'un champ de saisie, placer un bouton de validation à droite d'un input, créer un champ de recherche avec un bouton intégré — Flexbox simplifie considérablement ces mises en page.
.champ-recherche {
display: flex;
}
.champ-recherche input {
flex: 1; /* L'input prend tout l'espace disponible */
padding: 10px;
border: 1px solid #cccccc;
border-right: none;
}
.champ-recherche button {
padding: 10px 20px;
background-color: #2E6DA4;
color: white;
border: none;
cursor: pointer;
}
Flexbox vs CSS Grid : Flexbox est conçu pour la mise en page unidimensionnelle — une ligne ou une colonne à la fois. CSS Grid est conçu pour la mise en page bidimensionnelle — lignes et colonnes simultanément. En pratique, on utilise Flexbox pour les composants (navigations, cartes, formulaires) et Grid pour les grandes structures de page. Les deux coexistent parfaitement et se complètent.
Flexbox repose sur une relation entre deux types d'acteurs : le conteneur flex et les éléments flex. Comprendre cette distinction est la première chose à maîtriser avant d'aborder les propriétés. Tout le système de Flexbox s'organise autour de cette dualité : certaines propriétés s'appliquent au conteneur, d'autres aux éléments. Les confondre est la source d'erreurs la plus fréquente chez les débutants.
Un conteneur flex est simplement un élément HTML auquel on applique la déclaration display: flex. Cette unique instruction transforme radicalement le comportement de l'élément et de ses enfants directs.
.conteneur {
display: flex;
}
C'est tout. Une seule propriété suffit à activer Flexbox. À partir de ce moment, le navigateur applique automatiquement un ensemble de comportements par défaut que vous allez maintenant découvrir.
Ce qui change sur le conteneur lui-même :
Le conteneur reste un élément bloc dans le flux du document — il occupe toujours toute la largeur disponible et force un saut de ligne avant et après lui. Si vous voulez qu'un conteneur flex se comporte comme un élément inline, vous pouvez utiliser display: inline-flex.
.conteneur-bloc {
display: flex; /* Reste un bloc dans le flux */
}
.conteneur-inline {
display: inline-flex; /* Se comporte comme inline-block dans le flux,
mais ses enfants sont gérés par Flexbox */
}
Ce qui change sur les enfants directs — automatiquement, sans rien écrire :
Dès que display: flex est appliqué au parent, ses enfants directs deviennent des éléments flex et acquièrent de nouveaux comportements automatiques :
block ou inline d'origine : un span et un div se comportent de la même façon à l'intérieur d'un conteneur flex.Voici un exemple concret pour observer ces comportements par défaut :
<!-- exemple-flex-base.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="exemple-flex-base.css">
<title>Conteneur flex de base</title>
</head>
<body>
<h2>Sans Flexbox (comportement par défaut)</h2>
<div class="sans-flex">
<div class="boite">Boite 1</div>
<div class="boite">Boite 2</div>
<div class="boite">Boite 3</div>
</div>
<h2>Avec Flexbox (display: flex)</h2>
<div class="avec-flex">
<div class="boite">Boite 1</div>
<div class="boite">Boite 2 avec plus de texte</div>
<div class="boite">Boite 3</div>
</div>
</body>
</html>
/* exemple-flex-base.css */
body {
font-family: Arial, sans-serif;
padding: 30px;
background-color: #f4f4f4;
}
h2 {
color: #1A3A5C;
margin-bottom: 12px;
}
/* Sans Flexbox : les div s'empilent verticalement (comportement bloc) */
.sans-flex {
background-color: #e0e0e0;
padding: 10px;
margin-bottom: 40px;
}
/* Avec Flexbox : les div se placent côte à côte automatiquement */
.avec-flex {
display: flex; /* Une seule ligne active tout Flexbox */
background-color: #e0e0e0;
padding: 10px;
}
.boite {
background-color: #2E6DA4;
color: #ffffff;
padding: 20px;
margin: 5px;
font-size: 14px;
}
Observation clé : dans
.avec-flex, les trois boîtes sont côte à côte ET ont toutes la même hauteur, même si "Boite 2" contient plus de texte. Sans Flexbox, chaque boîte aurait sa propre hauteur déterminée par son contenu. C'est l'un des premiers cadeaux de Flexbox.
Les éléments flex sont les enfants directs d'un conteneur flex — ni plus, ni moins. Il est crucial de comprendre cette notion de directness : seuls les enfants immédiats sont concernés. Les petits-enfants, arrière-petits-enfants et autres descendants ne sont pas des éléments flex, sauf si leur propre parent est lui-même un conteneur flex.
<!-- identification-elements-flex.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="identification-elements-flex.css">
<title>Identification des éléments flex</title>
</head>
<body>
<div class="conteneur-flex">
<!-- ÉLÉMENT FLEX : enfant direct du conteneur -->
<div class="enfant-direct">
Enfant direct (élément flex)
<!-- PAS un élément flex : c'est un petit-enfant du conteneur -->
<p class="petit-enfant">
Petit-enfant (PAS un élément flex).
Il est géré par le flux normal à l'intérieur de son parent.
</p>
</div>
<!-- ÉLÉMENT FLEX : enfant direct du conteneur -->
<div class="enfant-direct">
Enfant direct (élément flex)
</div>
<!-- ÉLÉMENT FLEX : enfant direct, même si c'est un span (inline par défaut) -->
<span class="enfant-direct">
Span enfant direct (élément flex).
Même les éléments inline deviennent flex ici.
</span>
</div>
</body>
</html>
/* identification-elements-flex.css */
body {
font-family: Arial, sans-serif;
padding: 30px;
background-color: #f4f4f4;
}
.conteneur-flex {
display: flex;
background-color: #1A3A5C;
padding: 10px;
gap: 10px;
}
/* Les enfants directs deviennent automatiquement des éléments flex */
.enfant-direct {
background-color: #2E6DA4;
color: #ffffff;
padding: 16px;
font-size: 14px;
font-weight: bold;
}
/* Le petit-enfant n'est PAS un élément flex : il suit le flux normal
à l'intérieur de son parent .enfant-direct */
.petit-enfant {
background-color: #a8c4e0;
color: #1A3A5C;
padding: 8px;
margin-top: 8px;
font-weight: normal;
font-size: 13px;
}
Un élément flex perd son type d'affichage d'origine.
Avant de devenir un élément flex, chaque enfant avait un display propre : block pour un div, inline pour un span, list-item pour un li... Dès qu'il devient élément flex, ces comportements sont remplacés par le comportement flex. En particulier :
span (inline) peut maintenant avoir une largeur, une hauteur, des marges verticales — sans avoir à écrire display: block ou display: inline-block.div (block) ne force plus de saut de ligne — il se place côte à côte avec ses frères.<!-- flex-inline-vs-block.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="flex-inline-vs-block.css">
<title>Flex : inline vs block</title>
</head>
<body>
<h2>Sans Flexbox</h2>
<div class="sans-flex">
<div>div (bloc)</div>
<span>span (inline)</span>
<span>span (inline)</span>
</div>
<h2>Avec Flexbox</h2>
<div class="avec-flex">
<div>div (maintenant flex)</div>
<span>span (maintenant flex)</span>
<span>span (maintenant flex)</span>
</div>
</body>
</html>
/* flex-inline-vs-block.css */
body {
font-family: Arial, sans-serif;
padding: 30px;
background-color: #f4f4f4;
}
h2 {
color: #1A3A5C;
margin-bottom: 10px;
}
.sans-flex,
.avec-flex {
background-color: #e8e8e8;
padding: 10px;
margin-bottom: 40px;
}
.avec-flex {
display: flex;
gap: 10px;
}
/* On applique les mêmes styles à tous les enfants dans les deux cas */
.sans-flex div,
.sans-flex span,
.avec-flex div,
.avec-flex span {
background-color: #C0392B;
color: white;
padding: 16px 20px;
font-size: 14px;
/* On tente de donner une hauteur : ignorée sur span sans flex */
height: 80px;
}
Observation : sans Flexbox,
height: 80pxest ignoré sur lesspan(éléments inline). Avec Flexbox, la hauteur s'applique à tous les enfants, qu'ils soientdivouspan, car leur type d'affichage d'origine est remplacé par le comportement flex.
Les éléments flex peuvent eux-mêmes devenir des conteneurs flex. C'est l'un des points les plus puissants de Flexbox : un élément flex peut simultanément être un conteneur flex pour ses propres enfants. On appelle cela le flex imbriqué.
<!-- flex-imbrique.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="flex-imbrique.css">
<title>Flex imbriqué</title>
</head>
<body>
<div class="conteneur-principal">
<div class="carte">
<div class="carte-header">
<span class="titre">Projet Alpha</span>
<span class="statut">Actif</span>
</div>
<p>Description du projet Alpha et de ses objectifs principaux.</p>
</div>
<div class="carte">
<div class="carte-header">
<span class="titre">Projet Beta</span>
<span class="statut">En pause</span>
</div>
<p>Description du projet Beta et de son état d'avancement actuel.</p>
</div>
</div>
</body>
</html>
/* flex-imbrique.css */
body {
font-family: Arial, sans-serif;
padding: 30px;
background-color: #f4f4f4;
}
/* Conteneur principal : flex pour placer les cartes côte à côte */
.conteneur-principal {
display: flex;
gap: 20px;
}
/* .carte est un élément flex (enfant de .conteneur-principal)
ET un conteneur flex pour son propre contenu */
.carte {
flex: 1;
background-color: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 20px;
}
/* .carte-header est un conteneur flex pour placer titre et statut
sur la même ligne avec de l'espace entre eux */
.carte-header {
display: flex; /* Conteneur flex imbriqué */
justify-content: space-between; /* Titre à gauche, statut à droite */
align-items: center;
margin-bottom: 12px;
}
.titre {
font-weight: bold;
color: #1A3A5C;
font-size: 16px;
}
.statut {
font-size: 12px;
padding: 4px 10px;
border-radius: 20px;
background-color: #2E6DA4;
color: white;
}
.carte p {
font-size: 14px;
color: #666666;
line-height: 1.6;
margin: 0;
}
À retenir :
.conteneur-principalest un conteneur flex..carteest à la fois un élément flex (géré par.conteneur-principal) ET un conteneur flex ordinaire pour ses enfantsdivetp..carte-headerest lui-même un conteneur flex. Il n'y a aucune limite à l'imbrication des conteneurs flex.
Objectif : construire une rangée de cartes de profil côte à côte en utilisant display: flex sur le conteneur. L'exercice travaille l'activation d'un conteneur flex et l'observation des comportements automatiques des éléments flex : alignement côte à côte, égalisation des hauteurs, perte du comportement bloc/inline d'origine.
Consignes HTML :
profils.html.<div> avec la classe grille contenant quatre <div> avec la classe profil..profil contient :
<div> avec la classe avatar (initiale ou lettre au choix).<h3> avec un prénom.<p> avec la classe metier (un métier fictif).<p> avec la classe bio. Variez volontairement la longueur du texte entre les quatre profils pour observer l'égalisation des hauteurs.profils.css.Consignes :
.grille pour que les profils s'affichent côte à côte..profil a un fond blanc, une bordure, du padding et des coins arrondis. Son contenu est centré horizontalement..avatar est un cercle de 70px de diamètre, centré dans la carte, avec une initiale en grand.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
<title>Grille de profils</title>
</head>
<body>
<h1>Notre équipe</h1>
<div class="grille">
<div class="profil un">
<div class="avatar">A</div>
<h3>Alice Martin</h3>
<p class="metier">Designer UI</p>
<p class="bio">Spécialisée en design d'interfaces accessibles et en systèmes de design.Spécialisée en design d'interfaces accessibles et en systèmes de design.Spécialisée en design d'interfaces accessibles et en systèmes de design.Spécialisée en design d'interfaces accessibles et en systèmes de design.Spécialisée en design d'interfaces accessibles et en systèmes de design.</p>
</div>
<div class="profil deux">
<div class="avatar">B</div>
<h3>Baptiste Roux</h3>
<p class="metier">Développeur Front-End</p>
<p class="bio">Expert CSS et JavaScript. Passionné par les animations et les performances web. Contributeur open source depuis 2018.</p>
</div>
<div class="profil trois">
<div class="avatar">C</div>
<h3>Clara Dubois</h3>
<p class="metier">Chef de projet</p>
<p class="bio">Coordinatrice d'équipes techniques et garante de la qualité des livrables.</p>
</div>
<div class="profil quatre">
<div class="avatar">D</div>
<h3>David Lefort</h3>
<p class="metier">Développeur Back-End</p>
<p class="bio">Architecte de bases de données et d'API REST. Maîtrise Python, Node.js et PostgreSQL. Dix ans d'expérience en développement serveur pour des projets à forte charge.</p>
</div>
</div>
</body>
</html>
/* profils.css — Correction exercice 16 */
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 40px;
font-family: Arial, sans-serif;
background-color: #f0f4f8;
color: #2c2c2c;
}
h1 {
color: #1A3A5C;
margin-bottom: 30px;
}
/*
display: flex est la seule instruction qui place les quatre profils
côte à côte ET leur donne automatiquement la même hauteur.
gap gère l'espace entre les cartes sans calcul de marges manuelles.
*/
.grille {
display: flex;
gap: 20px;
}
.profil {
background-color: #ffffff;
border: 1px solid #dde3ed;
border-radius: 10px;
padding: 24px 20px;
text-align: center;
}
.un {
flex: 1.5;
}
.deux {
flex: 2;
}
.trois {
flex: 3;
}
.quatre {
flex: 1;
}
/*
.avatar est un <div> (bloc).
margin: 0 auto le centre horizontalement dans .profil.
border-radius: 50% le transforme en cercle.
line-height = height pour centrer l'initiale verticalement.
*/
.avatar {
width: 70px;
height: 70px;
border-radius: 50%;
background-color: #2E6DA4;
color: #ffffff;
font-size: 28px;
font-weight: bold;
line-height: 70px;
text-align: center;
margin: 0 auto 16px auto;
}
.profil h3 {
color: #1A3A5C;
font-size: 16px;
margin: 0 0 6px 0;
}
.metier {
color: #2E6DA4;
font-size: 13px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 0.5px;
margin: 0 0 12px 0;
}
.bio {
font-size: 13px;
color: #666666;
line-height: 1.7;
margin: 0;
}
display: flex sur .grille : cette unique déclaration remplace ce qui aurait nécessité float + clearfix + inline-block + font-size: 0 avec les techniques précédentes. Les quatre cartes se placent côte à côte immédiatement.
Égalisation automatique des hauteurs : observez que la carte de David, qui contient beaucoup plus de texte, est plus haute — et toutes les autres cartes s'étendent automatiquement à cette même hauteur. Avec float ou inline-block, chaque carte aurait eu sa propre hauteur. Ici, Flexbox gère cela sans aucune propriété supplémentaire.
Erreur fréquente : appliquer display: flex sur .profil au lieu de .grille. Flexbox agit sur les enfants directs du conteneur — si on l'applique sur la carte elle-même, c'est le contenu de la carte (avatar, titre, bio) qui devient flex, pas les cartes entre elles.
Flexbox organise ses éléments le long de deux axes perpendiculaires. Comprendre ces axes est absolument indispensable avant d'aborder les propriétés d'alignement : toutes les propriétés de Flexbox font référence à l'un ou l'autre de ces axes. Sans cette compréhension, les propriétés comme justify-content et align-items semblent arbitraires. Avec elle, elles deviennent parfaitement logiques.
L'axe principal est la direction dans laquelle les éléments flex se placent les uns à la suite des autres. Par défaut, cet axe est horizontal, de gauche à droite. C'est pourquoi, sans rien écrire d'autre que display: flex, les éléments se placent en ligne horizontale.
L'axe principal est défini par la propriété flex-direction, que vous étudierez en détail dans la section 7.4. Pour l'instant, retenez que l'axe principal peut être horizontal (gauche à droite, ou droite à gauche) ou vertical (haut en bas, ou bas en haut).
<!-- axes-flexbox.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="axes-flexbox.css">
<title>Axes Flexbox</title>
</head>
<body>
<h2>Axe principal horizontal (par défaut : flex-direction: row)</h2>
<div class="conteneur axe-horizontal">
<div class="boite">1</div>
<div class="boite">2</div>
<div class="boite">3</div>
</div>
<h2>Axe principal vertical (flex-direction: column)</h2>
<div class="conteneur axe-vertical">
<div class="boite">1</div>
<div class="boite">2</div>
<div class="boite">3</div>
</div>
</body>
</html>
/* axes-flexbox.css */
body {
font-family: Arial, sans-serif;
padding: 30px;
background-color: #f4f4f4;
}
h2 {
color: #1A3A5C;
margin-bottom: 12px;
font-size: 16px;
}
.conteneur {
display: flex;
background-color: #e0e8f5;
padding: 12px;
margin-bottom: 40px;
gap: 10px;
}
/* Axe principal horizontal : les boîtes vont de gauche à droite */
.axe-horizontal {
flex-direction: row; /* Valeur par défaut, peut être omise */
}
/* Axe principal vertical : les boîtes vont de haut en bas */
.axe-vertical {
flex-direction: column;
width: 200px; /* Limite la largeur pour rendre le résultat visible */
}
.boite {
background-color: #2E6DA4;
color: #ffffff;
padding: 20px 28px;
font-size: 18px;
font-weight: bold;
text-align: center;
border-radius: 4px;
}
Point clé : l'axe principal détermine la direction dans laquelle les éléments flex se succèdent. Toutes les propriétés de distribution de l'espace (
justify-content) agissent le long de cet axe.
L'axe secondaire — aussi appelé axe transversal ou cross axis — est perpendiculaire à l'axe principal. Quand l'axe principal est horizontal, l'axe secondaire est vertical, et vice versa.
L'axe secondaire contrôle comment les éléments sont alignés dans la direction perpendiculaire à leur sens de défilement. En pratique, c'est lui qui permet de centrer verticalement des éléments dans un conteneur horizontal — l'un des cas d'usage les plus fréquents de Flexbox.
<!-- axe-secondaire.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="axe-secondaire.css">
<title>Axe secondaire Flexbox</title>
</head>
<body>
<h2>Axe secondaire : alignement en haut (align-items: flex-start)</h2>
<div class="conteneur align-start">
<div class="boite grande">Grande boite</div>
<div class="boite">Petite</div>
<div class="boite">Petite</div>
</div>
<h2>Axe secondaire : alignement au centre (align-items: center)</h2>
<div class="conteneur align-center">
<div class="boite grande">Grande boite</div>
<div class="boite">Petite</div>
<div class="boite">Petite</div>
</div>
<h2>Axe secondaire : étirement complet (align-items: stretch — par défaut)</h2>
<div class="conteneur align-stretch">
<div class="boite grande">Grande boite</div>
<div class="boite">Petite</div>
<div class="boite">Petite</div>
</div>
</body>
</html>
/* axe-secondaire.css */
body {
font-family: Arial, sans-serif;
padding: 30px;
background-color: #f4f4f4;
}
h2 {
color: #1A3A5C;
margin-bottom: 12px;
font-size: 16px;
}
.conteneur {
display: flex;
background-color: #e0e8f5;
padding: 12px;
margin-bottom: 40px;
gap: 10px;
height: 140px; /* Hauteur fixe pour rendre l'axe secondaire visible */
}
.align-start { align-items: flex-start; }
.align-center { align-items: center; }
.align-stretch { align-items: stretch; } /* Valeur par défaut */
.boite {
background-color: #2E6DA4;
color: #ffffff;
padding: 16px 24px;
font-size: 14px;
font-weight: bold;
text-align: center;
border-radius: 4px;
}
/* La grande boite a une hauteur explicite pour forcer la différence */
.grande {
height: 100px;
background-color: #1A3A5C;
line-height: 68px; /* Centrage vertical du texte dans la grande boite */
}
Le comportement
stretchpar défaut : sans aucune instruction d'alignement, les éléments flex s'étirent automatiquement pour atteindre la hauteur du conteneur (ou la hauteur du plus grand élément). C'est pourquoi, dans l'exercice 16, toutes les cartes de profil avaient la même hauteur sans qu'on ait eu besoin de l'écrire : c'estalign-items: stretchqui s'applique par défaut.
Récapitulatif des deux axes selon flex-direction :
flex-direction |
Axe principal | Axe secondaire |
|---|---|---|
row (défaut) |
Horizontal (gauche → droite) | Vertical (haut → bas) |
row-reverse |
Horizontal (droite → gauche) | Vertical (haut → bas) |
column |
Vertical (haut → bas) | Horizontal (gauche → droite) |
column-reverse |
Vertical (bas → haut) | Horizontal (gauche → droite) |
À retenir absolument : quand
flex-directionchange, les axes s'échangent. Ce qui était l'axe principal devient secondaire, et inversement. Cela signifie quejustify-contentetalign-itemséchangent aussi leur rôle. C'est une source fréquente de confusion — gardez ce tableau en tête.
Objectif : manipuler directement les deux axes de Flexbox en faisant varier flex-direction et en observant comment les éléments se réorganisent. L'exercice ancre visuellement la notion d'axe principal et d'axe secondaire avant d'aborder les propriétés d'alignement.
Consignes HTML :
axes.html.<h2> descriptif et un <div class="conteneur"> contenant cinq <div class="item"> numérotés de 1 à 5.row, row-reverse, column, column-reverse.axes.css.Consignes :
.conteneur ont display: flex, un fond coloré, du padding et une hauteur fixe de 200px.flex-direction..item sont des blocs colorés, numérotés, avec du padding. Leur contenu est centré.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="axes.css">
<title>Les deux axes de Flexbox</title>
</head>
<body>
<h1>Les axes de Flexbox</h1>
<h2>flex-direction: row (défaut) — axe principal : horizontal →</h2>
<div class="conteneur row">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
</div>
<h2>flex-direction: row-reverse — axe principal : horizontal ←</h2>
<div class="conteneur row-reverse">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
</div>
<h2>flex-direction: column — axe principal : vertical ↓</h2>
<div class="conteneur column">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
</div>
<h2>flex-direction: column-reverse — axe principal : vertical ↑</h2>
<div class="conteneur column-reverse">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
</div>
</body>
</html>
/* axes.css — Correction exercice 17 */
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 30px;
font-family: Arial, sans-serif;
background-color: #f4f4f4;
color: #2c2c2c;
}
h1 {
color: #1A3A5C;
margin-bottom: 30px;
}
h2 {
color: #2E6DA4;
font-size: 15px;
margin-bottom: 10px;
margin-top: 40px;
}
.conteneur {
display: flex;
background-color: #e0e8f5;
padding: 12px;
gap: 10px;
}
/* Chaque classe applique une direction différente */
.row { flex-direction: row; }
.row-reverse { flex-direction: row-reverse; }
.column { flex-direction: column; }
.column-reverse { flex-direction: column-reverse; }
.item {
background-color: #2E6DA4;
color: #ffffff;
font-size: 20px;
font-weight: bold;
padding: 10px 20px;
border-radius: 4px;
/*
En row/row-reverse : les items s'étirent sur toute la hauteur (stretch par défaut).
En column/column-reverse : les items s'étirent sur toute la largeur (stretch par défaut).
*/
display: flex;
align-items: center;
justify-content: center;
}
row et row-reverse : l'axe principal est horizontal. Les items se placent de gauche à droite (row) ou de droite à gauche (row-reverse). L'axe secondaire est vertical — les items s'étirent sur toute la hauteur du conteneur grâce à stretch.
column et column-reverse : l'axe principal devient vertical. Les items se placent de haut en bas (column) ou de bas en haut (column-reverse). L'axe secondaire devient horizontal — les items s'étirent sur toute la largeur du conteneur.
L'échange des axes : observez attentivement que dans row, les items occupent toute la hauteur (axe secondaire = vertical, stretch). Dans column, les items occupent toute la largeur (axe secondaire = horizontal, stretch). C'est la même propriété par défaut (align-items: stretch) qui produit ces deux comportements différents selon la direction.
Erreur fréquente : croire que justify-content aligne toujours horizontalement et align-items toujours verticalement. C'est faux. justify-content agit sur l'axe principal — qui peut être vertical si flex-direction: column. align-items agit sur l'axe secondaire — qui peut être horizontal dans ce même cas. Les propriétés suivent les axes, pas les directions absolues.
Les propriétés du conteneur flex sont celles qui s'appliquent sur l'élément parent — celui sur lequel vous écrivez display: flex. Elles contrôlent la direction des éléments, leur comportement face au manque d'espace, et leur alignement sur les deux axes. Ces propriétés sont le cœur de Flexbox et celles que vous utiliserez dans la quasi-totalité de vos mises en page.
La propriété flex-direction définit l'axe principal, c'est-à-dire la direction dans laquelle les éléments flex se succèdent. Vous avez vu ses quatre valeurs dans la section 7.3 — voici maintenant comment les utiliser en contexte réel.
<!-- flex-direction.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="flex-direction.css">
<title>flex-direction</title>
</head>
<body>
<h2>row — navigation horizontale classique</h2>
<nav class="nav-horizontale">
<a href="#">Accueil</a>
<a href="#">Articles</a>
<a href="#">Portfolio</a>
<a href="#">Contact</a>
</nav>
<h2>column — menu vertical</h2>
<nav class="nav-verticale">
<a href="#">Accueil</a>
<a href="#">Articles</a>
<a href="#">Portfolio</a>
<a href="#">Contact</a>
</nav>
<h2>row-reverse — éléments dans l'ordre inversé</h2>
<div class="liste-inversee">
<div class="tag">HTML</div>
<div class="tag">CSS</div>
<div class="tag">JavaScript</div>
</div>
</body>
</html>
/* flex-direction.css */
body {
font-family: Arial, sans-serif;
padding: 30px;
background-color: #f4f4f4;
}
h2 { color: #1A3A5C; margin: 30px 0 10px; font-size: 15px; }
.nav-horizontale {
display: flex;
flex-direction: row;
gap: 4px;
background-color: #1A3A5C;
padding: 0 16px;
}
.nav-horizontale a {
display: block;
padding: 14px 18px;
color: #ffffff;
text-decoration: none;
}
.nav-horizontale a:hover { background-color: #2E6DA4; }
.nav-verticale {
display: flex;
flex-direction: column;
width: 200px;
background-color: #1A3A5C;
}
.nav-verticale a {
padding: 12px 20px;
color: #ffffff;
text-decoration: none;
border-bottom: 1px solid #2E6DA4;
}
.nav-verticale a:hover { background-color: #2E6DA4; }
.liste-inversee {
display: flex;
flex-direction: row-reverse;
gap: 8px;
}
.tag {
background-color: #2E6DA4;
color: white;
padding: 6px 16px;
border-radius: 20px;
font-size: 13px;
}
Cas d'usage de
row-reverseetcolumn-reverse: l'inversion de l'ordre visuel est utile pour l'accessibilité (garder un ordre HTML logique pour les lecteurs d'écran tout en inversant l'affichage) et pour les interfaces chat où le dernier message doit apparaître en bas.
Par défaut, les éléments flex ne passent jamais à la ligne. Si le conteneur est trop étroit, ils se compriment pour tenir sur une seule ligne. La propriété flex-wrap contrôle ce comportement.
.conteneur { flex-wrap: nowrap; } /* Défaut : tout sur une ligne */
.conteneur { flex-wrap: wrap; } /* Passage à la ligne si besoin */
.conteneur { flex-wrap: wrap-reverse; } /* Passage à la ligne vers le haut */
<!-- flex-wrap.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="flex-wrap.css">
<title>flex-wrap</title>
</head>
<body>
<h2>nowrap (défaut) — les éléments se compriment sur une seule ligne</h2>
<div class="conteneur nowrap">
<div class="carte">Carte 1</div>
<div class="carte">Carte 2</div>
<div class="carte">Carte 3</div>
<div class="carte">Carte 4</div>
<div class="carte">Carte 5</div>
</div>
<h2>wrap — les éléments passent à la ligne suivante</h2>
<div class="conteneur wrap">
<div class="carte">Carte 1</div>
<div class="carte">Carte 2</div>
<div class="carte">Carte 3</div>
<div class="carte">Carte 4</div>
<div class="carte">Carte 5</div>
</div>
</body>
</html>
/* flex-wrap.css */
body {
font-family: Arial, sans-serif;
padding: 30px;
background-color: #f4f4f4;
}
h2 { color: #1A3A5C; margin: 30px 0 10px; font-size: 15px; }
.conteneur {
display: flex;
gap: 10px;
background-color: #e0e8f5;
padding: 12px;
margin-bottom: 30px;
}
.nowrap { flex-wrap: nowrap; }
.wrap { flex-wrap: wrap; }
.carte {
min-width: 160px;
background-color: #2E6DA4;
color: white;
padding: 20px;
text-align: center;
border-radius: 4px;
font-weight: bold;
}
flex-wrap: wrapet le responsive : combiné à unemin-widthsur les éléments,flex-wrap: wrapcrée des grilles naturellement responsives sans media query. Quand le conteneur est trop étroit, les éléments passent à la ligne automatiquement.
La propriété raccourcie flex-flow combine flex-direction et flex-wrap :
/* Ces deux écritures sont équivalentes */
.conteneur {
flex-direction: row;
flex-wrap: wrap;
}
.conteneur {
flex-flow: row wrap;
}
Trois propriétés gèrent l'alignement : justify-content pour l'axe principal, align-items pour l'axe secondaire, et align-content pour les conteneurs multi-lignes.
justify-content — distribution sur l'axe principal.
<!-- justify-content.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="justify-content.css">
<title>justify-content</title>
</head>
<body>
<h2>flex-start (défaut)</h2>
<div class="conteneur jc-start">
<div class="boite">A</div><div class="boite">B</div><div class="boite">C</div>
</div>
<h2>flex-end</h2>
<div class="conteneur jc-end">
<div class="boite">A</div><div class="boite">B</div><div class="boite">C</div>
</div>
<h2>center</h2>
<div class="conteneur jc-center">
<div class="boite">A</div><div class="boite">B</div><div class="boite">C</div>
</div>
<h2>space-between — premier et dernier aux extrémités</h2>
<div class="conteneur jc-between">
<div class="boite">A</div><div class="boite">B</div><div class="boite">C</div>
</div>
<h2>space-around — espace égal autour de chaque élément</h2>
<div class="conteneur jc-around">
<div class="boite">A</div><div class="boite">B</div><div class="boite">C</div>
</div>
<h2>space-evenly — espace strictement identique partout</h2>
<div class="conteneur jc-evenly">
<div class="boite">A</div><div class="boite">B</div><div class="boite">C</div>
</div>
</body>
</html>
/* justify-content.css */
body { font-family: Arial, sans-serif; padding: 30px; background-color: #f4f4f4; }
h2 { color: #1A3A5C; margin: 24px 0 8px; font-size: 14px; }
.conteneur {
display: flex;
background-color: #e0e8f5;
padding: 12px;
margin-bottom: 8px;
}
.jc-start { justify-content: flex-start; }
.jc-end { justify-content: flex-end; }
.jc-center { justify-content: center; }
.jc-between { justify-content: space-between; }
.jc-around { justify-content: space-around; }
.jc-evenly { justify-content: space-evenly; }
.boite {
background-color: #2E6DA4;
color: white;
width: 60px;
height: 60px;
line-height: 60px;
text-align: center;
font-size: 20px;
font-weight: bold;
border-radius: 4px;
}
align-items — alignement sur l'axe secondaire.
<!-- align-items.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="align-items.css">
<title>align-items</title>
</head>
<body>
<h2>stretch (défaut) — éléments étirés sur toute la hauteur</h2>
<div class="conteneur ai-stretch">
<div class="boite">A</div><div class="boite haute">B</div><div class="boite">C</div>
</div>
<h2>flex-start — éléments alignés en haut</h2>
<div class="conteneur ai-start">
<div class="boite">A</div><div class="boite haute">B</div><div class="boite">C</div>
</div>
<h2>center — éléments centrés verticalement</h2>
<div class="conteneur ai-center">
<div class="boite">A</div><div class="boite haute">B</div><div class="boite">C</div>
</div>
<h2>flex-end — éléments alignés en bas</h2>
<div class="conteneur ai-end">
<div class="boite">A</div><div class="boite haute">B</div><div class="boite">C</div>
</div>
<h2>baseline — alignement sur la ligne de base du texte</h2>
<div class="conteneur ai-baseline">
<div class="boite petit-texte">Aa</div>
<div class="boite grand-texte">Aa</div>
<div class="boite petit-texte">Aa</div>
</div>
</body>
</html>
/* align-items.css */
body { font-family: Arial, sans-serif; padding: 30px; background-color: #f4f4f4; }
h2 { color: #1A3A5C; margin: 24px 0 8px; font-size: 14px; }
.conteneur {
display: flex;
background-color: #e0e8f5;
padding: 12px;
gap: 10px;
height: 120px;
margin-bottom: 8px;
}
.ai-stretch { align-items: stretch; }
.ai-start { align-items: flex-start; }
.ai-center { align-items: center; }
.ai-end { align-items: flex-end; }
.ai-baseline { align-items: baseline; }
.boite {
background-color: #2E6DA4;
color: white;
padding: 10px 20px;
font-weight: bold;
border-radius: 4px;
text-align: center;
}
.haute { height: 70px; background-color: #1A3A5C; line-height: 50px; }
.petit-texte { font-size: 13px; padding-top: 20px; }
.grand-texte { font-size: 32px; }
baselineen pratique : aligne les éléments sur la ligne de base typographique — la ligne imaginaire sur laquelle repose le bas des lettres. Utile quand des éléments avec des tailles de police différentes doivent paraître alignés de façon cohérente.
align-content — alignement des lignes dans un conteneur multi-lignes.
Ne s'applique que quand flex-wrap: wrap est actif. Contrôle comment les lignes sont distribuées sur l'axe secondaire, à la manière de justify-content pour les éléments.
.conteneur {
display: flex;
flex-wrap: wrap;
align-content: flex-start; /* Lignes groupées en haut */
align-content: center; /* Lignes groupées au centre */
align-content: space-between; /* Espace égal entre les lignes */
align-content: stretch; /* Lignes étirées (valeur par défaut) */
}
Exemple:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Align Content Demo</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
</div>
</body>
</html>body {
margin: 0;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: #f2f2f2;
}
.container {
display: flex;
flex-wrap: wrap; /* OBLIGATOIRE */
width: 300px;
height: 400px; /* Plus grand que le contenu */
background: white;
border: 2px solid black;
align-content: center; /* 👈 La star ici */
gap: 10px;
}
.item {
width: 80px;
height: 80px;
background: steelblue;
color: white;
display: flex;
justify-content: center;
align-items: center;
}Objectif : construire le tableau de bord d'une application fictive : une barre de navigation, une rangée de quatre cartes de statistiques et un pied de page. Chaque zone mobilise une combinaison différente de justify-content et align-items.
Consignes HTML :
dashboard.html.<header class="navbar"> contenant un <span class="logo"> et une <nav> avec trois <a> : Vue d'ensemble, Statistiques, Paramètres.<main class="contenu"> contenant quatre <div class="stat-card">. Chaque carte contient un <p class="stat-label"> et un <p class="stat-valeur">.<footer> avec un <p> de copyright et un <span class="version">.dashboard.css.Consignes :
.navbar est un conteneur flex : logo à gauche, liens à droite, tout centré verticalement. La navbar a une hauteur fixe de 60px..stat-card s'affichent côte à côte avec un espace identique entre elles. Le contenu de chaque carte est centré horizontalement et verticalement à l'intérieur.<footer> est un conteneur flex : copyright à gauche, version à droite, centrés verticalement.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="dashboard.css">
<title>Tableau de bord</title>
</head>
<body>
<header class="navbar">
<span class="logo">AppDash</span>
<nav>
<a href="#">Vue d'ensemble</a>
<a href="#">Statistiques</a>
<a href="#">Paramètres</a>
</nav>
</header>
<main class="contenu">
<div class="stat-card">
<p class="stat-label">Utilisateurs actifs</p>
<p class="stat-valeur">12 480</p>
</div>
<div class="stat-card">
<p class="stat-label">Commandes du jour</p>
<p class="stat-valeur">847</p>
</div>
<div class="stat-card">
<p class="stat-label">Chiffre d'affaires</p>
<p class="stat-valeur">38 920 €</p>
</div>
<div class="stat-card">
<p class="stat-label">Taux de conversion</p>
<p class="stat-valeur">6,8 %</p>
</div>
</main>
<footer>
<p>© 2025 AppDash. Tous droits réservés.</p>
<span class="version">v2.4.1</span>
</footer>
</body>
</html>
/* dashboard.css — Correction exercice 18 */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
background-color: #f0f4f8;
color: #2c2c2c;
}
/*
justify-content: space-between pousse le logo à gauche
et la nav à droite.
align-items: center centre verticalement sans line-height couplé.
*/
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #1A3A5C;
padding: 0 30px;
height: 60px;
}
.logo {
color: #ffffff;
font-size: 20px;
font-weight: bold;
}
.navbar nav {
display: flex;
gap: 4px;
}
.navbar nav a {
display: block;
padding: 8px 16px;
color: #a8c4e0;
text-decoration: none;
border-radius: 4px;
font-size: 14px;
transition: background-color 0.2s ease, color 0.2s ease;
}
.navbar nav a:hover {
background-color: #2E6DA4;
color: #ffffff;
}
/*
justify-content: space-between distribue l'espace entre les cartes.
Les cartes s'étirent automatiquement à la même hauteur (stretch par défaut).
*/
.contenu {
display: flex;
justify-content: space-between;
padding: 40px 30px;
gap: 20px;
}
/*
Chaque carte est elle-même un conteneur flex en colonne
pour centrer son contenu dans les deux axes.
*/
.stat-card {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 1;
background-color: #ffffff;
border-radius: 10px;
padding: 30px 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
text-align: center;
}
.stat-label {
font-size: 13px;
color: #888888;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 12px;
}
.stat-valeur {
font-size: 32px;
font-weight: bold;
color: #1A3A5C;
}
/* Même pattern que la navbar */
footer {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #1A3A5C;
padding: 16px 30px;
color: #a8c4e0;
font-size: 13px;
}
.version {
background-color: #2E6DA4;
color: #ffffff;
padding: 3px 10px;
border-radius: 10px;
font-size: 12px;
}
justify-content: space-between pour la navbar et le footer : place le premier enfant au début et le dernier à la fin, tout l'espace restant se répartit entre eux. C'est le pattern le plus courant pour les barres avec un élément à gauche et un autre à droite.
align-items: center sans line-height couplé : comparez avec l'exercice 15. Ici, le centrage vertical de la navbar est obtenu en une propriété, indépendamment de la hauteur. Si demain la navbar passe à 80px, rien d'autre à modifier.
Flex imbriqué sur .stat-card : chaque carte est à la fois un élément flex géré par .contenu, et un conteneur flex pour son propre contenu. flex-direction: column + justify-content: center + align-items: center centre verticalement et horizontalement le label et la valeur.
Erreur fréquente : inverser justify-content et align-items quand flex-direction: row. Dans ce cas, justify-content agit horizontalement et align-items verticalement — les confondre produit un résultat inattendu.
Objectif : construire une section de tarification avec trois offres côte à côte. L'offre centrale est mise en avant grâce à align-items: flex-end combiné à un padding plus généreux. L'exercice travaille justify-content, align-items et flex-wrap dans un cas concret de mise en page de cartes de tailles différentes.
Consignes HTML :
tarifs.html.<section class="tarification"> avec un <h1> et une <div class="offres">..offres, créez trois <div> avec la classe offre et les classes supplémentaires essentiel, pro, entreprise..offre contient : un <h2>, un <p class="prix">, un <ul> de quatre <li> et un <a class="btn-offre">.tarifs.css.Consignes :
.offres est un conteneur flex. Les trois offres sont centrées horizontalement dans la section et alignées par le bas — ce qui fait naturellement dépasser vers le haut la carte .pro qui est plus haute..offre a une largeur fixe de 260px, un fond blanc, du padding et une ombre..pro a davantage de padding vertical et une couleur de fond distincte..btn-offre est un bouton pleine largeur.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="tarifs.css">
<title>Tarification</title>
</head>
<body>
<section class="tarification">
<h1>Choisissez votre offre</h1>
<div class="offres">
<div class="offre essentiel">
<h2>Essentiel</h2>
<p class="prix">9 € <span class="periode">/mois</span></p>
<ul>
<li>3 projets</li>
<li>5 Go de stockage</li>
<li>Support par email</li>
<li>Mises à jour incluses</li>
</ul>
<a href="#" class="btn-offre">Commencer</a>
</div>
<div class="offre pro">
<h2>Pro</h2>
<p class="prix">29 € <span class="periode">/mois</span></p>
<ul>
<li>Projets illimités</li>
<li>50 Go de stockage</li>
<li>Support prioritaire</li>
<li>Accès API inclus</li>
</ul>
<a href="#" class="btn-offre">Choisir Pro</a>
</div>
<div class="offre entreprise">
<h2>Entreprise</h2>
<p class="prix">79 € <span class="periode">/mois</span></p>
<ul>
<li>Tout de Pro</li>
<li>Stockage illimité</li>
<li>Support dédié 24h/7j</li>
<li>SSO & audit logs</li>
</ul>
<a href="#" class="btn-offre">Nous contacter</a>
</div>
</div>
</section>
</body>
</html>
/* tarifs.css — Correction exercice 19 */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
background-color: #f0f4f8;
color: #2c2c2c;
}
.tarification {
padding: 60px 30px;
}
.tarification h1 {
text-align: center;
color: #1A3A5C;
font-size: 28px;
margin-bottom: 40px;
}
/*
justify-content: center groupe les offres au centre de la page.
align-items: flex-end aligne toutes les cartes par le bas :
la carte .pro, plus haute grâce à son padding supérieur,
dépasse naturellement vers le haut — effet classique de mise en avant.
*/
.offres {
display: flex;
justify-content: center;
align-items: flex-end;
gap: 20px;
}
.offre {
width: 260px;
background-color: #ffffff;
border: 1px solid #dde3ed;
border-radius: 10px;
padding: 30px 24px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.07);
text-align: center;
}
/* Padding vertical plus grand = carte plus haute = dépasse vers le haut */
.pro {
background-color: #1A3A5C;
border-color: #1A3A5C;
padding: 46px 24px;
color: #ffffff;
}
.offre h2 {
font-size: 20px;
margin-bottom: 16px;
color: #1A3A5C;
}
.pro h2 { color: #ffffff; }
.prix {
font-size: 32px;
font-weight: bold;
color: #1A3A5C;
margin-bottom: 24px;
}
.pro .prix { color: #ffffff; }
.periode {
font-size: 14px;
font-weight: normal;
color: #888888;
}
.pro .periode { color: #a8c4e0; }
.offre ul {
list-style: none;
margin-bottom: 28px;
padding: 0;
}
.offre ul li {
padding: 8px 0;
font-size: 14px;
color: #555555;
border-bottom: 1px solid #f0f0f0;
}
.offre ul li:last-child { border-bottom: none; }
.pro ul li {
color: #d0e4f5;
border-bottom-color: #2E6DA4;
}
.btn-offre {
display: block;
background-color: #2E6DA4;
color: #ffffff;
padding: 12px;
text-decoration: none;
border-radius: 6px;
font-weight: bold;
font-size: 14px;
transition: background-color 0.2s ease;
}
.btn-offre:hover { background-color: #1A3A5C; }
.pro .btn-offre {
background-color: #ffffff;
color: #1A3A5C;
}
.pro .btn-offre:hover { background-color: #a8c4e0; }
align-items: flex-end : toutes les cartes sont alignées par leur bord inférieur. La carte Pro, ayant plus de padding vertical, est naturellement plus haute — elle dépasse vers le haut. C'est un pattern de design très courant pour les pages de tarification, obtenu ici en une seule propriété Flexbox.
justify-content: center : les trois cartes ont une largeur fixe de 260px. Sans cette propriété, elles seraient groupées à gauche. center les place au milieu de la section quelle que soit la largeur de l'écran.
Erreur fréquente : tenter d'obtenir l'effet de surélévation avec margin-top: -30px ou position: relative. Ces approches fonctionnent mais sont fragiles. align-items: flex-end combiné à un padding plus grand est la solution Flexbox naturelle et robuste.
Jusqu'ici, toutes les propriétés étudiées s'appliquaient sur le conteneur. Les propriétés de cette section s'appliquent sur les éléments flex eux-mêmes — les enfants directs du conteneur. Elles permettent de contrôler comment chaque élément se comporte individuellement face à l'espace disponible : est-ce qu'il grandit ? Est-ce qu'il rétrécit ? Quelle est sa taille de départ ?
flex-grow — la croissance.
flex-grow définit la capacité d'un élément à grandir pour occuper l'espace disponible dans le conteneur. Sa valeur est un nombre sans unité qui représente une proportion.
<!-- flex-grow.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="flex-grow.css">
<title>flex-grow</title>
</head>
<body>
<h2>flex-grow: 0 sur tous (défaut) — les éléments prennent leur taille naturelle</h2>
<div class="conteneur">
<div class="item g0">A</div>
<div class="item g0">B</div>
<div class="item g0">C</div>
</div>
<h2>flex-grow: 1 sur tous — l'espace est partagé équitablement</h2>
<div class="conteneur">
<div class="item g1">A</div>
<div class="item g1">B</div>
<div class="item g1">C</div>
</div>
<h2>flex-grow: 1 / 2 / 1 — B prend deux fois plus d'espace que A et C</h2>
<div class="conteneur">
<div class="item g1">A</div>
<div class="item g2">B</div>
<div class="item g1">C</div>
</div>
<h2>flex-grow: 0 / 1 / 0 — seul B s'étire pour combler l'espace</h2>
<div class="conteneur">
<div class="item g0">A</div>
<div class="item g1">B</div>
<div class="item g0">C</div>
</div>
</body>
</html>
/* flex-grow.css */
body { font-family: Arial, sans-serif; padding: 30px; background-color: #f4f4f4; }
h2 { color: #1A3A5C; margin: 24px 0 8px; font-size: 14px; }
.conteneur {
display: flex;
background-color: #e0e8f5;
padding: 10px;
gap: 10px;
margin-bottom: 8px;
}
.item {
background-color: #2E6DA4;
color: white;
padding: 16px;
text-align: center;
font-weight: bold;
font-size: 18px;
border-radius: 4px;
}
.g0 { flex-grow: 0; } /* Défaut : ne grandit pas */
.g1 { flex-grow: 1; }
.g2 { flex-grow: 2; } /* Prend deux fois plus d'espace libre que flex-grow: 1 */
Comment fonctionne le calcul :
flex-growne définit pas une largeur absolue. Il définit la proportion de l'espace libre que chaque élément reçoit. Si le conteneur a 300px d'espace non occupé et que trois éléments ontflex-grow: 1, chacun reçoit 100px supplémentaires. Si l'un aflex-grow: 2et les deux autresflex-grow: 1, le premier reçoit 150px et les deux autres 75px chacun.
flex-shrink — la réduction.
flex-shrink définit la capacité d'un élément à rétrécir quand l'espace disponible est insuffisant. Sa valeur est également un nombre proportionnel. La valeur par défaut est 1 — tous les éléments rétrécissent proportionnellement par défaut.
<!-- flex-shrink.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="flex-shrink.css">
<title>flex-shrink</title>
</head>
<body>
<h2>flex-shrink: 1 sur tous (défaut) — tous rétrécissent proportionnellement</h2>
<div class="conteneur">
<div class="item s1">A — 200px</div>
<div class="item s1">B — 200px</div>
<div class="item s1">C — 200px</div>
</div>
<h2>flex-shrink: 0 sur B — B refuse de rétrécir, A et C absorbent le manque</h2>
<div class="conteneur">
<div class="item s1">A — 200px</div>
<div class="item s0">B — 200px (ne rétrécit pas)</div>
<div class="item s1">C — 200px</div>
</div>
</body>
</html>
/* flex-shrink.css */
body { font-family: Arial, sans-serif; padding: 30px; background-color: #f4f4f4; }
h2 { color: #1A3A5C; margin: 24px 0 8px; font-size: 14px; }
/* Conteneur délibérément étroit pour forcer le rétrécissement */
.conteneur {
display: flex;
width: 500px;
background-color: #e0e8f5;
padding: 10px;
gap: 8px;
margin-bottom: 8px;
}
.item {
width: 200px; /* 3 x 200px = 600px > 500px : il faut rétrécir */
background-color: #2E6DA4;
color: white;
padding: 16px 10px;
text-align: center;
font-size: 13px;
font-weight: bold;
border-radius: 4px;
}
.s1 { flex-shrink: 1; } /* Rétrécit normalement (défaut) */
.s0 { flex-shrink: 0; background-color: #C0392B; } /* Refuse de rétrécir */
flex-shrink: 0en pratique : cette valeur est très utilisée pour les éléments qui ne doivent jamais se comprimer — une icône dans une barre de navigation, un avatar dans une carte de profil, un logo. Sansflex-shrink: 0, ces éléments pourraient se déformer sur les petits écrans.
flex-basis — le point de départ.
flex-basis définit la taille initiale d'un élément flex, avant que flex-grow et flex-shrink n'entrent en jeu. C'est le point de départ des calculs de distribution de l'espace.
.item { flex-basis: auto; } /* Défaut : la taille est déterminée par le contenu
ou par width/height si défini */
.item { flex-basis: 200px; } /* Taille de base fixe de 200px */
.item { flex-basis: 33.333%; } /* Taille de base proportionnelle */
.item { flex-basis: 0; } /* Taille de base nulle : tout l'espace est "libre"
et réparti selon flex-grow */
<!-- flex-basis.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="flex-basis.css">
<title>flex-basis</title>
</head>
<body>
<h2>flex-basis: auto (défaut) — taille déterminée par le contenu</h2>
<div class="conteneur">
<div class="item">Contenu court</div>
<div class="item">Contenu beaucoup plus long qui prend plus de place</div>
<div class="item">Moyen</div>
</div>
<h2>flex-basis: 200px — taille de départ fixe identique</h2>
<div class="conteneur">
<div class="item basis-200">Contenu court</div>
<div class="item basis-200">Contenu beaucoup plus long qui prend plus de place</div>
<div class="item basis-200">Moyen</div>
</div>
<h2>flex-basis: 0 + flex-grow: 1 — distribution purement proportionnelle</h2>
<div class="conteneur">
<div class="item basis-0">Contenu court</div>
<div class="item basis-0">Contenu beaucoup plus long</div>
<div class="item basis-0">Moyen</div>
</div>
</body>
</html>
/* flex-basis.css */
body { font-family: Arial, sans-serif; padding: 30px; background-color: #f4f4f4; }
h2 { color: #1A3A5C; margin: 24px 0 8px; font-size: 14px; }
.conteneur {
display: flex;
background-color: #e0e8f5;
padding: 10px;
gap: 10px;
margin-bottom: 8px;
}
.item {
background-color: #2E6DA4;
color: white;
padding: 16px;
font-size: 13px;
border-radius: 4px;
}
.basis-200 {
flex-basis: 200px;
/* Taille de départ à 200px pour chaque élément.
Avec flex-grow: 0 par défaut, ils gardent cette taille si l'espace suffit. */
}
.basis-0 {
flex-basis: 0;
flex-grow: 1;
/* Taille de départ nulle : TOUT l'espace du conteneur est considéré
comme libre et distribué à parts égales par flex-grow: 1.
Le contenu n'influence plus la taille de départ. */
}
La propriété raccourcie flex.
Dans la pratique, flex-grow, flex-shrink et flex-basis sont presque toujours écrits ensemble via la propriété raccourcie flex. C'est la forme recommandée car elle initialise automatiquement les valeurs non précisées de manière cohérente.
/* Syntaxe complète */
.item { flex: <flex-grow> <flex-shrink> <flex-basis>; }
/* Exemples courants */
.item { flex: 1; }
/* Équivalent à : flex-grow: 1, flex-shrink: 1, flex-basis: 0
L'élément grandit et rétrécit librement, taille de base nulle.
C'est la valeur la plus utilisée pour des colonnes de taille égale. */
.item { flex: auto; }
/* Équivalent à : flex-grow: 1, flex-shrink: 1, flex-basis: auto
L'élément grandit et rétrécit, mais en partant de sa taille naturelle. */
.item { flex: none; }
/* Équivalent à : flex-grow: 0, flex-shrink: 0, flex-basis: auto
L'élément ne grandit ni ne rétrécit. Taille figée à son contenu. */
.item { flex: 0 0 250px; }
/* L'élément ne grandit pas, ne rétrécit pas, fait toujours 250px. */
.item { flex: 2 1 300px; }
/* Taille de base 300px, grandit deux fois plus vite que flex: 1,
rétrécit normalement. */
<!-- flex-raccourci.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="flex-raccourci.css">
<title>Propriété raccourcie flex</title>
</head>
<body>
<h2>Sidebar fixe + contenu flexible — pattern très courant</h2>
<div class="layout">
<aside class="sidebar">Sidebar<br>fixe 260px</aside>
<main class="principal">Contenu principal — prend tout l'espace restant</main>
</div>
<h2>Trois colonnes : petite / grande / petite</h2>
<div class="trois-col">
<div class="col-small">Petite<br>flex: 1</div>
<div class="col-large">Grande<br>flex: 2</div>
<div class="col-small">Petite<br>flex: 1</div>
</div>
</body>
</html>
/* flex-raccourci.css */
body { font-family: Arial, sans-serif; padding: 30px; background-color: #f4f4f4; }
h2 { color: #1A3A5C; margin: 24px 0 8px; font-size: 14px; }
/* Pattern sidebar + contenu : le plus fréquent en mise en page */
.layout {
display: flex;
gap: 20px;
margin-bottom: 40px;
height: 150px;
}
.sidebar {
flex: 0 0 260px; /* Ne grandit pas, ne rétrécit pas, toujours 260px */
background-color: #1A3A5C;
color: white;
padding: 20px;
border-radius: 4px;
font-size: 14px;
}
.principal {
flex: 1; /* Prend tout l'espace restant après la sidebar */
background-color: #2E6DA4;
color: white;
padding: 20px;
border-radius: 4px;
font-size: 14px;
}
/* Trois colonnes avec proportions différentes */
.trois-col {
display: flex;
gap: 10px;
height: 100px;
}
.col-small {
flex: 1; /* 1 part de l'espace disponible */
background-color: #2E6DA4;
color: white;
padding: 16px;
text-align: center;
border-radius: 4px;
font-size: 13px;
}
.col-large {
flex: 2; /* 2 parts de l'espace disponible */
background-color: #1A3A5C;
color: white;
padding: 16px;
text-align: center;
border-radius: 4px;
font-size: 13px;
}
La propriété align-self — alignement individuel.
Toutes les propriétés d'alignement vues jusqu'ici s'appliquaient à tous les éléments du conteneur. align-self permet de surcharger align-items pour un seul élément, en lui donnant un alignement différent de ses frères.
<!-- align-self.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="align-self.css">
<title>align-self</title>
</head>
<body>
<h2>align-items: center sur le conteneur, mais chaque élément peut se positionner individuellement</h2>
<div class="conteneur">
<div class="item self-start">flex-start</div>
<div class="item self-center">center (hérite)</div>
<div class="item self-end">flex-end</div>
<div class="item self-stretch">stretch</div>
</div>
</body>
</html>
/* align-self.css */
body { font-family: Arial, sans-serif; padding: 30px; background-color: #f4f4f4; }
h2 { color: #1A3A5C; margin: 24px 0 8px; font-size: 14px; }
.conteneur {
display: flex;
align-items: center; /* Valeur par défaut pour tous les enfants */
background-color: #e0e8f5;
padding: 12px;
gap: 10px;
height: 160px;
}
.item {
background-color: #2E6DA4;
color: white;
padding: 16px;
border-radius: 4px;
font-size: 13px;
font-weight: bold;
text-align: center;
}
/* Chaque élément surcharge align-items du conteneur */
.self-start { align-self: flex-start; background-color: #C0392B; }
.self-center { align-self: center; } /* Même que le conteneur : identique */
.self-end { align-self: flex-end; background-color: #1E6B2E; }
.self-stretch { align-self: stretch; background-color: #8E44AD; }
align-selfen pratique : très utile pour les cas où un seul élément doit se comporter différemment des autres — par exemple un bouton "Supprimer" aligné en bas d'une carte alors que le reste est centré, ou un logo aligné en haut dans une navbar alors que les liens sont centrés.
Objectif : construire la mise en page d'un article de blog avec une colonne principale et une sidebar fixe. L'exercice travaille flex: 1 sur le contenu principal et flex: 0 0 <valeur> sur la sidebar pour créer un layout robuste où la sidebar reste à largeur constante quelle que soit la taille de l'écran.
Consignes HTML :
article-blog.html.<header> avec un <h1> (titre du blog) et une <nav> avec trois liens.<div class="page"> contenant :
<article class="contenu-principal"> avec un <h2>, trois <p> de texte et une <div class="tags"> contenant quatre <span class="tag">.<aside class="sidebar"> contenant un <h3>, une <ul> de cinq liens, et un second bloc avec un <h3> et un <p> de description.<footer> avec un <p>.article-blog.css.Consignes :
<header> est un conteneur flex avec le titre à gauche et la navigation à droite, centrés verticalement..page est un conteneur flex : .contenu-principal prend tout l'espace disponible, .sidebar reste à largeur fixe et ne rétrécit jamais..tag sont des éléments inline qui se placent naturellement côte à côte — pas besoin de Flexbox sur leur conteneur.<footer> est centré horizontalement.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="article-blog.css">
<title>Article de blog</title>
</head>
<body>
<header class="header-blog">
<h1>Le Blog du Dev</h1>
<nav>
<a href="#">Accueil</a>
<a href="#">Articles</a>
<a href="#">À propos</a>
</nav>
</header>
<div class="page">
<article class="contenu-principal">
<h2>Flexbox : la propriété qui a changé la mise en page CSS</h2>
<p>Pendant des années, les développeurs web ont dû contourner les limites du modèle de boîte traditionnel pour créer des mises en page en colonnes. Les flottants, détournés de leur usage typographique originel, ont permis de construire des interfaces complètes mais au prix d'une complexité croissante.</p>
<p>L'arrivée de Flexbox a changé radicalement la donne. En s'appliquant sur le conteneur plutôt que sur chaque élément individuellement, Flexbox offre un niveau d'abstraction bien supérieur. Les calculs manuels de pourcentages, les clearfix et les problèmes de hauteur inégale appartiennent désormais au passé.</p>
<p>Aujourd'hui, Flexbox est supporté par tous les navigateurs modernes avec une compatibilité quasi universelle. Il est devenu l'outil de référence pour les mises en page de composants, tandis que CSS Grid prend en charge les grandes structures de page. Les deux technologies sont complémentaires et s'utilisent conjointement dans la majorité des projets.</p>
<div class="tags">
<span class="tag">CSS</span>
<span class="tag">Flexbox</span>
<span class="tag">Mise en page</span>
<span class="tag">Tutoriel</span>
</div>
</article>
<aside class="sidebar">
<div class="widget">
<h3>Articles récents</h3>
<ul>
<li><a href="#">Introduction à CSS Grid</a></li>
<li><a href="#">Les variables CSS natives</a></li>
<li><a href="#">Media queries et responsive</a></li>
<li><a href="#">Animations CSS modernes</a></li>
<li><a href="#">Optimiser ses sélecteurs CSS</a></li>
</ul>
</div>
<div class="widget">
<h3>À propos</h3>
<p>Ce blog est dédié au développement web front-end. Retrouvez des tutoriels, des bonnes pratiques et des retours d'expérience sur le CSS, JavaScript et les outils modernes du web.</p>
</div>
</aside>
</div>
<footer class="footer-blog">
<p>© 2024 Le Blog du Dev. Tous droits réservés.</p>
</footer>
</body>
</html>
/* article-blog.css — Correction exercice 20 */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Georgia, serif;
background-color: #f5f5f0;
color: #2c2c2c;
}
/* Header */
.header-blog {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #1A3A5C;
padding: 0 30px;
height: 64px;
}
.header-blog h1 {
color: #ffffff;
font-size: 22px;
letter-spacing: 1px;
}
.header-blog nav {
display: flex;
gap: 4px;
}
.header-blog nav a {
display: block;
padding: 8px 14px;
color: #a8c4e0;
text-decoration: none;
font-size: 14px;
font-family: Arial, sans-serif;
border-radius: 4px;
transition: background-color 0.2s ease, color 0.2s ease;
}
.header-blog nav a:hover {
background-color: #2E6DA4;
color: #ffffff;
}
/* Layout principal */
.page {
display: flex;
gap: 30px;
max-width: 1000px;
margin: 0 auto;
padding: 40px 20px;
}
/*
flex: 1 — l'article prend tout l'espace disponible après la sidebar.
Il grandit et rétrécit librement selon la largeur de la fenêtre.
*/
.contenu-principal {
flex: 1;
background-color: #ffffff;
padding: 32px;
border: 1px solid #e0ddd6;
}
.contenu-principal h2 {
color: #1A3A5C;
font-size: 22px;
line-height: 1.4;
margin-bottom: 20px;
}
.contenu-principal p {
font-size: 15px;
line-height: 1.9;
color: #444444;
margin-bottom: 16px;
}
/* Les tags sont des <span> inline : ils se placent naturellement côte à côte */
.tags {
margin-top: 24px;
padding-top: 20px;
border-top: 1px solid #e8e4dc;
}
.tag {
display: inline-block;
background-color: #e8f0fe;
color: #2E6DA4;
font-family: Arial, sans-serif;
font-size: 12px;
font-weight: bold;
padding: 4px 12px;
border-radius: 20px;
margin-right: 6px;
margin-bottom: 6px;
}
/*
flex: 0 0 260px — la sidebar ne grandit pas (0), ne rétrécit pas (0),
et fait toujours 260px (260px).
Sans le flex-shrink: 0, la sidebar pourrait se comprimer
sur les écrans moyens, ce qui casserait le layout.
*/
.sidebar {
flex: 0 0 260px;
}
.widget {
background-color: #ffffff;
border: 1px solid #e0ddd6;
padding: 20px;
margin-bottom: 20px;
}
.widget h3 {
color: #1A3A5C;
font-size: 16px;
margin-bottom: 14px;
padding-bottom: 10px;
border-bottom: 2px solid #2E6DA4;
font-family: Arial, sans-serif;
}
.widget ul {
list-style: none;
}
.widget ul li {
padding: 7px 0;
border-bottom: 1px solid #f0ece4;
}
.widget ul li:last-child { border-bottom: none; }
.widget ul li a {
color: #2E6DA4;
text-decoration: none;
font-family: Arial, sans-serif;
font-size: 14px;
transition: color 0.2s ease;
}
.widget ul li a:hover { color: #1A3A5C; }
.widget p {
font-size: 13px;
line-height: 1.7;
color: #666666;
}
/* Footer */
.footer-blog {
background-color: #1A3A5C;
color: #a8c4e0;
text-align: center;
padding: 20px;
font-family: Arial, sans-serif;
font-size: 13px;
}
flex: 1 sur .contenu-principal : cette valeur est l'équivalent de flex: 1 1 0. L'article part d'une taille de base nulle et prend tout l'espace disponible dans le conteneur après que la sidebar a occupé ses 260px. Si la fenêtre s'élargit, l'article s'élargit. Si elle rétrécit, l'article rétrécit. La sidebar, elle, reste intacte.
flex: 0 0 260px sur .sidebar : les deux premiers 0 signifient que la sidebar ne grandit jamais (flex-grow: 0) et ne rétrécit jamais (flex-shrink: 0). Le 260px est sa flex-basis — sa taille permanente. C'est le pattern fondamental de toute mise en page avec sidebar fixe en Flexbox.
Erreur fréquente : écrire seulement width: 260px sur la sidebar sans flex-shrink: 0. Sur un écran plus étroit, Flexbox comprimera quand même la sidebar si nécessaire car flex-shrink: 1 est la valeur par défaut. Le résultat : la sidebar rétrécit alors qu'on ne le souhaitait pas.
Objectif : construire une section de fonctionnalités avec des cartes de tailles proportionnelles différentes. Une fonctionnalité principale prend deux fois plus de place que les secondaires. L'exercice travaille flex-grow avec des proportions variées, flex-wrap pour le passage à la ligne et align-self pour une carte qui se distingue verticalement.
Consignes HTML :
Créez un fichier fonctionnalites.html.
Créez une <section class="section-fonc"> avec un <h1> et une <div class="grille">.
Dans .grille, créez cinq <div class="fonc"> avec les classes supplémentaires : fonc-principale, fonc-secondaire (x3) et fonc-cta.
Chaque .fonc contient un <div class="fonc-icone"> (une initiale), un <h2> et un <p>.
.fonc-cta contient en plus un <a class="btn"> avec le texte En savoir plus.
Liez un fichier fonctionnalites.css.
Consignes :
.grille est un conteneur flex avec flex-wrap: wrap et un gap..fonc-principale a un flex qui lui donne deux fois plus de place que les cartes secondaires..fonc-secondaire ont un flex égal entre elles..fonc-cta a la même taille qu'une .fonc-secondaire mais s'aligne en bas du conteneur grâce à align-self, quelle que soit la hauteur des autres cartes..fonc a un fond blanc, du padding, une bordure et des coins arrondis.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="fonctionnalites.css">
<title>Fonctionnalités</title>
</head>
<body>
<section class="section-fonc">
<h1>Tout ce dont vous avez besoin</h1>
<div class="grille">
<div class="fonc fonc-principale">
<div class="fonc-icone">P</div>
<h2>Performance optimisée</h2>
<p>Notre infrastructure est conçue pour offrir des temps de réponse inférieurs à 100ms, même sous forte charge. Les données sont distribuées sur plusieurs régions pour garantir une disponibilité maximale et une latence minimale pour vos utilisateurs, où qu'ils se trouvent dans le monde.</p>
</div>
<div class="fonc fonc-secondaire">
<div class="fonc-icone">S</div>
<h2>Sécurité renforcée</h2>
<p>Chiffrement de bout en bout et authentification multi-facteurs inclus dans tous les plans.</p>
</div>
<div class="fonc fonc-secondaire">
<div class="fonc-icone">A</div>
<h2>API complète</h2>
<p>Une API REST documentée pour intégrer nos services dans votre stack technique existant.</p>
</div>
<div class="fonc fonc-secondaire">
<div class="fonc-icone">R</div>
<h2>Rapports détaillés</h2>
<p>Tableaux de bord en temps réel et exports CSV pour analyser vos données en profondeur.</p>
</div>
<div class="fonc fonc-cta">
<div class="fonc-icone">+</div>
<h2>Et bien plus encore</h2>
<p>Découvrez l'ensemble de nos fonctionnalités et trouvez l'offre adaptée à vos besoins.</p>
<a href="#" class="btn">En savoir plus</a>
</div>
</div>
</section>
</body>
</html>
/* fonctionnalites.css — Correction exercice 21 */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
background-color: #f0f4f8;
color: #2c2c2c;
}
.section-fonc {
max-width: 1000px;
margin: 0 auto;
padding: 60px 20px;
}
.section-fonc h1 {
color: #1A3A5C;
font-size: 28px;
text-align: center;
margin-bottom: 40px;
}
/*
flex-wrap: wrap : les cartes passent à la ligne si l'espace manque.
align-items: stretch (défaut) : toutes les cartes d'une même ligne
ont la même hauteur.
*/
.grille {
display: flex;
flex-wrap: wrap;
gap: 20px;
align-items: stretch;
}
/* Styles communs à toutes les cartes */
.fonc {
background-color: #ffffff;
border: 1px solid #dde3ed;
border-radius: 10px;
padding: 28px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
/*
flex: 2 — la carte principale prend deux fois plus d'espace
que les cartes secondaires (flex: 1).
min-width évite qu'elle devienne trop étroite sur petits écrans.
*/
.fonc-principale {
flex: 2;
min-width: 300px;
border-top: 4px solid #1A3A5C;
}
/*
flex: 1 — les cartes secondaires se partagent équitablement l'espace.
min-width force un passage à la ligne sur les petits écrans.
*/
.fonc-secondaire {
flex: 1;
min-width: 180px;
}
/*
Même taille qu'une .fonc-secondaire.
align-self: flex-end la colle au bas du conteneur.
Sur la même ligne que les fonc-secondaire, elle apparaîtra
alignée par le bas plutôt que par le haut (flex-start).
*/
.fonc-cta {
flex: 1;
min-width: 180px;
align-self: flex-end;
border-top: 4px solid #2E6DA4;
}
.fonc-icone {
width: 48px;
height: 48px;
border-radius: 10px;
background-color: #2E6DA4;
color: #ffffff;
font-size: 22px;
font-weight: bold;
line-height: 48px;
text-align: center;
margin-bottom: 16px;
}
.fonc-principale .fonc-icone {
background-color: #1A3A5C;
}
.fonc h2 {
font-size: 16px;
color: #1A3A5C;
margin-bottom: 10px;
}
.fonc p {
font-size: 14px;
color: #666666;
line-height: 1.7;
}
.btn {
display: block;
margin-top: 20px;
background-color: #2E6DA4;
color: #ffffff;
text-align: center;
padding: 10px;
text-decoration: none;
border-radius: 6px;
font-size: 14px;
font-weight: bold;
transition: background-color 0.2s ease;
}
.btn:hover { background-color: #1A3A5C; }
flex: 2 vs flex: 1 : la carte principale reçoit deux fois plus d'espace libre que chaque carte secondaire. Si la grille a 900px d'espace libre et que les cinq cartes ont respectivement flex: 2, 1, 1, 1, 1 (soit 6 parts en tout), la principale reçoit 300px et chaque secondaire 150px supplémentaires, en plus de leur taille de base nulle.
align-self: flex-end sur .fonc-cta : alors que toutes les autres cartes sont étirées par align-items: stretch pour avoir la même hauteur, .fonc-cta surcharge cette règle et se colle au bas du conteneur. Elle garde sa hauteur naturelle et laisse un espace vide au-dessus. C'est align-self qui permet ce comportement individuel sans toucher aux autres cartes.
min-width et flex-wrap : les min-width sur chaque carte définissent le seuil en dessous duquel elles ne peuvent pas rétrécir. Combinés à flex-wrap: wrap, les cartes passent à la ligne suivante quand leur min-width ne peut plus être respectée. C'est la technique Flexbox pour créer des grilles responsives légères.
Cette section consolide tout ce que vous avez appris sur Flexbox en l'appliquant aux situations que vous rencontrerez systématiquement dans vos projets. Ce ne sont pas de nouveaux concepts — ce sont les mêmes propriétés, combinées intelligemment pour résoudre des problèmes concrets et récurrents.
La navigation est sans doute le composant où Flexbox brille le plus. Chaque type de menu a ses particularités, mais tous reposent sur les mêmes combinaisons de propriétés.
Navigation horizontale avec logo et liens.
Le pattern le plus courant : logo à gauche, liens à droite. justify-content: space-between sur le conteneur fait tout le travail.
<!-- menu-horizontal.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="menu-horizontal.css">
<title>Menu horizontal</title>
</head>
<body>
<header class="header">
<a href="#" class="logo">Marque</a>
<nav class="nav-principale">
<a href="#">Accueil</a>
<a href="#">Services</a>
<a href="#">Références</a>
<a href="#">Blog</a>
<a href="#" class="btn-contact">Contact</a>
</nav>
</header>
<main style="padding: 40px; font-family: Arial, sans-serif; color: #555;">
<p>Contenu de la page...</p>
</main>
</body>
</html>
/* menu-horizontal.css */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: Arial, sans-serif; background-color: #f4f4f4; }
.header {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #1A3A5C;
padding: 0 30px;
height: 64px;
}
.logo {
color: #ffffff;
font-size: 20px;
font-weight: bold;
text-decoration: none;
letter-spacing: 1px;
}
.nav-principale {
display: flex;
align-items: center;
gap: 4px;
}
.nav-principale a {
display: block;
padding: 8px 16px;
color: #a8c4e0;
text-decoration: none;
font-size: 14px;
border-radius: 4px;
transition: background-color 0.2s ease, color 0.2s ease;
}
.nav-principale a:hover {
background-color: #2E6DA4;
color: #ffffff;
}
/* Le bouton Contact se démarque des autres liens */
.btn-contact {
background-color: #2E6DA4;
color: #ffffff !important;
border-radius: 4px;
}
.btn-contact:hover {
background-color: #3a84c4 !important;
}
Navigation avec un élément poussé à droite via margin-left: auto.
Parfois, un seul élément doit être isolé à droite sans que tout le groupe de liens le soit. margin-left: auto sur cet élément consomme tout l'espace disponible et le pousse à l'extrémité droite.
<!-- menu-margin-auto.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="menu-margin-auto.css">
<title>Menu margin auto</title>
</head>
<body>
<header class="header">
<a href="#" class="logo">Marque</a>
<a href="#">Accueil</a>
<a href="#">Services</a>
<a href="#">Blog</a>
<!-- margin-left: auto sur cet élément le pousse tout à droite -->
<a href="#" class="compte">Mon compte</a>
</header>
</body>
</html>
/* menu-margin-auto.css */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: Arial, sans-serif; }
.header {
display: flex;
align-items: center;
background-color: #1A3A5C;
padding: 0 30px;
height: 64px;
gap: 4px;
}
.logo {
color: #ffffff;
font-size: 20px;
font-weight: bold;
text-decoration: none;
margin-right: 20px; /* Espace entre le logo et les liens */
}
.header a {
display: block;
padding: 8px 16px;
color: #a8c4e0;
text-decoration: none;
font-size: 14px;
border-radius: 4px;
transition: background-color 0.2s ease, color 0.2s ease;
}
.header a:hover {
background-color: #2E6DA4;
color: #ffffff;
}
/*
margin-left: auto consomme tout l'espace horizontal disponible
à gauche de .compte, le poussant tout à droite.
Les autres liens restent groupés à gauche avec le logo.
*/
.compte {
margin-left: auto;
background-color: #2E6DA4;
color: #ffffff !important;
}
margin: autodans un contexte flex : dans un conteneur flex,margin: autose comporte différemment du flux normal. Une marge auto absorbe tout l'espace disponible dans la direction concernée.margin-left: autosur un élément le pousse à l'extrémité droite.margin: autosur un seul élément le centre dans le conteneur. C'est une technique puissante et souvent plus simple que de diviser le conteneur en plusieurs groupes.
La création de colonnes côte à côte est l'un des cas d'usage les plus fréquents de Flexbox. Selon les besoins, plusieurs combinaisons de propriétés s'appliquent.
Colonnes de taille égale.
<!-- colonnes-egales.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="colonnes-egales.css">
<title>Colonnes égales</title>
</head>
<body>
<div class="grille">
<div class="col">
<h3>Colonne 1</h3>
<p>Contenu de la première colonne. Peu importe la quantité de texte, toutes les colonnes ont la même largeur.</p>
</div>
<div class="col">
<h3>Colonne 2</h3>
<p>Contenu de la deuxième colonne avec un peu plus de texte pour illustrer l'égalisation automatique des hauteurs par Flexbox.</p>
</div>
<div class="col">
<h3>Colonne 3</h3>
<p>Contenu bref.</p>
</div>
</div>
</body>
</html>
/* colonnes-egales.css */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: Arial, sans-serif; padding: 30px; background-color: #f4f4f4; }
.grille {
display: flex;
gap: 20px;
}
/* flex: 1 = flex: 1 1 0 : toutes les colonnes partent de 0
et se partagent l'espace équitablement. */
.col {
flex: 1;
background-color: #ffffff;
border: 1px solid #dde3ed;
border-radius: 8px;
padding: 24px;
}
.col h3 { color: #1A3A5C; margin-bottom: 10px; }
.col p { font-size: 14px; color: #666; line-height: 1.7; }
Colonnes de taille proportionnelle.
<!-- colonnes-proportionnelles.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="colonnes-proportionnelles.css">
<title>Colonnes proportionnelles</title>
</head>
<body>
<div class="page">
<main class="contenu">
<h2>Contenu principal</h2>
<p>Cette colonne prend les deux tiers de l'espace disponible grâce à flex: 2. Elle est naturellement plus large pour accueillir le contenu principal de la page.</p>
<p>C'est le pattern classique d'un article avec une sidebar : le contenu principal large, la sidebar plus étroite.</p>
</main>
<aside class="sidebar">
<h3>Sidebar</h3>
<p>Un tiers de l'espace. Parfait pour des liens, des résumés ou des widgets secondaires.</p>
</aside>
</div>
</body>
</html>
/* colonnes-proportionnelles.css */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: Arial, sans-serif; padding: 30px; background-color: #f4f4f4; }
.page {
display: flex;
gap: 20px;
}
/* flex: 2 — prend 2/3 de l'espace total */
.contenu {
flex: 2;
background-color: #ffffff;
border: 1px solid #dde3ed;
padding: 24px;
border-radius: 8px;
}
/* flex: 1 — prend 1/3 de l'espace total */
.sidebar {
flex: 1;
background-color: #e8f0fe;
border: 1px solid #c8daf0;
padding: 24px;
border-radius: 8px;
}
.contenu h2, .sidebar h3 { color: #1A3A5C; margin-bottom: 12px; }
.contenu p, .sidebar p { font-size: 14px; color: #555; line-height: 1.7; margin-bottom: 10px; }
Colonnes responsives avec flex-wrap.
<!-- colonnes-responsives.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="colonnes-responsives.css">
<title>Colonnes responsives</title>
</head>
<body>
<div class="grille">
<div class="carte">Carte 1</div>
<div class="carte">Carte 2</div>
<div class="carte">Carte 3</div>
<div class="carte">Carte 4</div>
<div class="carte">Carte 5</div>
<div class="carte">Carte 6</div>
</div>
</body>
</html>
/* colonnes-responsives.css */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: Arial, sans-serif; padding: 30px; background-color: #f4f4f4; }
.grille {
display: flex;
flex-wrap: wrap; /* Les cartes passent à la ligne si l'espace manque */
gap: 16px;
}
/*
flex: 1 1 200px :
- grandit pour remplir l'espace disponible
- rétrécit si nécessaire
- mais jamais en dessous de 200px (flex-basis)
Résultat : sur grand écran, 3-4 cartes par ligne.
Sur petit écran, 2 cartes, puis 1 seule. Sans media query.
*/
.carte {
flex: 1 1 200px;
background-color: #2E6DA4;
color: #ffffff;
padding: 30px 20px;
text-align: center;
border-radius: 8px;
font-weight: bold;
font-size: 15px;
}
Voici les patterns d'alignement les plus utilisés en production, avec leur implémentation directe.
Centrage complet dans une zone. Centrer un élément horizontalement et verticalement dans son conteneur — le cas le plus demandé.
<!-- centrage-complet.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="centrage-complet.css">
<title>Centrage complet</title>
</head>
<body>
<div class="hero">
<div class="hero-contenu">
<h1>Bienvenue</h1>
<p>Un message centré horizontalement et verticalement dans la zone.</p>
<a href="#" class="btn">Commencer</a>
</div>
</div>
<div class="icone-wrapper">
<span class="icone">★</span>
</div>
</body>
</html>
/* centrage-complet.css */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: Arial, sans-serif; background-color: #f4f4f4; }
/* Centrage d'un bloc de contenu dans une grande zone */
.hero {
display: flex;
justify-content: center;
align-items: center;
height: 300px;
background-color: #1A3A5C;
text-align: center;
}
.hero-contenu h1 { color: #fff; font-size: 32px; margin-bottom: 12px; }
.hero-contenu p { color: #a8c4e0; font-size: 15px; margin-bottom: 20px; }
.btn {
display: inline-block;
background-color: #2E6DA4;
color: #ffffff;
padding: 10px 28px;
text-decoration: none;
border-radius: 4px;
font-weight: bold;
}
/* Centrage d'une icône dans un carré */
.icone-wrapper {
display: flex;
justify-content: center;
align-items: center;
width: 80px;
height: 80px;
background-color: #2E6DA4;
border-radius: 50%;
margin: 30px auto 0;
}
.icone {
color: #ffffff;
font-size: 32px;
}
Éléments d'une carte alignés en bas. Dans une liste de cartes avec des contenus inégaux, le bouton doit toujours apparaître en bas de la carte, quelle que soit la quantité de texte.
<!-- carte-bouton-bas.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="carte-bouton-bas.css">
<title>Bouton en bas de carte</title>
</head>
<body>
<div class="grille">
<div class="carte">
<h3>Offre Essentielle</h3>
<p>Une courte description.</p>
<a href="#" class="btn-carte">Choisir</a>
</div>
<div class="carte">
<h3>Offre Professionnelle</h3>
<p>Une description beaucoup plus longue qui occupe plusieurs lignes et qui repousse naturellement le bouton vers le bas de la carte, créant un alignement inégal sans technique particulière.</p>
<a href="#" class="btn-carte">Choisir</a>
</div>
<div class="carte">
<h3>Offre Entreprise</h3>
<p>Description de longueur intermédiaire pour cette offre dédiée aux grandes structures.</p>
<a href="#" class="btn-carte">Choisir</a>
</div>
</div>
</body>
</html>
/* carte-bouton-bas.css */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: Arial, sans-serif; padding: 30px; background-color: #f4f4f4; }
/* Le conteneur de cartes */
.grille {
display: flex;
gap: 20px;
align-items: stretch; /* Toutes les cartes ont la même hauteur */
}
/*
Chaque carte est elle-même un conteneur flex en colonne.
Le contenu (titre, texte) s'empile normalement.
margin-top: auto sur le bouton consomme tout l'espace vertical restant
et le colle au bas de la carte, quelle que soit la hauteur du texte.
*/
.carte {
flex: 1;
display: flex;
flex-direction: column;
background-color: #ffffff;
border: 1px solid #dde3ed;
border-radius: 8px;
padding: 24px;
}
.carte h3 {
color: #1A3A5C;
font-size: 16px;
margin-bottom: 12px;
}
.carte p {
font-size: 14px;
color: #666;
line-height: 1.7;
margin-bottom: 0;
}
/* margin-top: auto pousse le bouton tout en bas de la carte */
.btn-carte {
display: block;
margin-top: auto;
padding-top: 20px; /* Espace minimal entre le texte et le bouton */
color: #2E6DA4;
text-decoration: none;
font-weight: bold;
font-size: 14px;
border-top: 1px solid #e8e8e8;
margin-left: -24px;
margin-right: -24px;
padding: 14px 24px 0;
transition: color 0.2s ease;
}
.btn-carte:hover { color: #1A3A5C; }
margin-top: autopour pousser vers le bas : dans un conteneur flex en colonne (flex-direction: column),margin-top: autosur un élément consomme tout l'espace vertical disponible au-dessus de lui. C'est la technique la plus propre pour garder un bouton en bas d'une carte quelle que soit la hauteur du contenu. Elle est préférable àposition: absolutequi sort l'élément du flux.
Objectif : construire une barre de navigation avec trois zones distinctes : un logo à gauche, des liens au centre, et un groupe d'actions (icône utilisateur + bouton) à droite. L'exercice travaille les différentes techniques de distribution avec justify-content, margin-left: auto et le flex imbriqué pour les sous-groupes.
Consignes HTML :
navbar-complete.html.<header class="navbar"> contenant :
<a class="logo"> avec le nom du site.<nav class="liens-centre"> avec quatre <a>.<div class="actions"> contenant un <span class="user"> (initiale de l'utilisateur) et un <a class="btn-app"> avec le texte Tableau de bord.<main> avec quelques lignes de contenu.navbar-complete.css.Consignes :
.navbar est un conteneur flex. Le .logo est à gauche, .liens-centre est centré dans la navbar, et .actions est à droite..liens-centre et .actions sont eux-mêmes des conteneurs flex..user est un cercle avec une initiale, stylisé comme un avatar..liens-centre entre le logo et les actions tout en gardant les actions à droite.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="navbar-complete.css">
<title>Navbar complète</title>
</head>
<body>
<header class="navbar">
<a href="#" class="logo">Plateforme</a>
<nav class="liens-centre">
<a href="#">Découvrir</a>
<a href="#">Solutions</a>
<a href="#">Tarifs</a>
<a href="#">Documentation</a>
</nav>
<div class="actions">
<span class="user">M</span>
<a href="#" class="btn-app">Tableau de bord</a>
</div>
</header>
<main class="page-contenu">
<h1>Bienvenue sur la plateforme</h1>
<p>Ceci est le contenu principal de la page.</p>
</main>
</body>
</html>
/* navbar-complete.css — Correction exercice 22 */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: Arial, sans-serif;
background-color: #f0f4f8;
}
/*
La technique pour centrer .liens-centre entre le logo et les actions :
donner à .logo et .actions la même flex: 1.
.logo prend 1/3 de l'espace, .liens-centre prend 1/3, .actions prend 1/3.
En centrant le contenu de .logo à gauche et de .actions à droite,
.liens-centre reste naturellement centré au milieu.
Alternative avec margin auto : margin-left: auto sur .liens-centre
et margin-left: auto sur .actions — mais cela ne centre pas parfaitement.
*/
.navbar {
display: flex;
align-items: center;
background-color: #1A3A5C;
padding: 0 24px;
height: 64px;
}
.logo {
flex: 1; /* Prend 1/3 de la largeur */
color: #ffffff;
font-size: 18px;
font-weight: bold;
text-decoration: none;
letter-spacing: 1px;
}
/* .liens-centre est un conteneur flex pour ses propres liens */
.liens-centre {
flex: 1; /* Prend 1/3 de la largeur */
display: flex;
justify-content: center; /* Centre les liens dans ce tiers */
align-items: center;
gap: 2px;
}
.liens-centre a {
display: block;
padding: 8px 14px;
color: #a8c4e0;
text-decoration: none;
font-size: 14px;
border-radius: 4px;
transition: background-color 0.2s ease, color 0.2s ease;
}
.liens-centre a:hover {
background-color: #2E6DA4;
color: #ffffff;
}
/* .actions est un conteneur flex aligné à droite */
.actions {
flex: 1; /* Prend 1/3 de la largeur */
display: flex;
justify-content: flex-end; /* Pousse le contenu à droite dans son tiers */
align-items: center;
gap: 12px;
}
.user {
display: flex;
justify-content: center;
align-items: center;
width: 36px;
height: 36px;
border-radius: 50%;
background-color: #2E6DA4;
color: #ffffff;
font-weight: bold;
font-size: 15px;
flex-shrink: 0; /* Ne rétrécit jamais */
}
.btn-app {
display: block;
padding: 7px 16px;
background-color: #2E6DA4;
color: #ffffff;
text-decoration: none;
border-radius: 4px;
font-size: 13px;
font-weight: bold;
white-space: nowrap;
transition: background-color 0.2s ease;
}
.btn-app:hover { background-color: #3a84c4; }
.page-contenu {
max-width: 800px;
margin: 60px auto;
padding: 0 20px;
color: #2c2c2c;
}
.page-contenu h1 { color: #1A3A5C; margin-bottom: 12px; }
.page-contenu p { font-size: 15px; line-height: 1.7; color: #555; }
Centrer .liens-centre entre le logo et les actions : la technique consiste à donner flex: 1 aux trois éléments directs de la navbar — logo, liens, actions — pour qu'ils se partagent chacun un tiers de la largeur totale. Ensuite, justify-content: center sur .liens-centre centre ses liens dans son tiers, et justify-content: flex-end sur .actions pousse son contenu à droite dans son tiers. Le logo, étant un élément flex seul dans son tiers, s'aligne naturellement à gauche.
flex-shrink: 0 sur .user : l'avatar est un cercle de taille fixe. Sans flex-shrink: 0, il pourrait se déformer sur les petits écrans. C'est le même principe que pour la sidebar de l'exercice 20.
Erreur fréquente : utiliser uniquement justify-content: space-between sur la navbar pour les trois zones. Cela fonctionne pour avoir logo à gauche et actions à droite, mais ne centre pas parfaitement .liens-centre — il se retrouverait calé à gauche des actions plutôt qu'au milieu géométrique de la navbar.
Objectif : construire une page de résultats de recherche avec une barre de recherche en haut, des filtres en colonne à gauche, et une liste de résultats à droite. Chaque résultat est une carte avec un titre, une description et des métadonnées alignées en bas. L'exercice mobilise l'ensemble des propriétés vues dans le module : layout en colonnes, flex imbriqué, flex-direction: column avec margin-top: auto et flex-wrap.
Consignes HTML :
recherche.html.<header> avec une <div class="barre-recherche"> contenant un <input> et un <button>.<div class="layout"> contenant :
<aside class="filtres"> avec un <h2> et trois <div class="groupe-filtre">, chacun avec un <h3> et trois <label> contenant chacun un <input type="checkbox"> et un texte.<section class="resultats"> avec quatre <article class="resultat">. Chaque article contient un <h2>, un <p class="description"> et une <div class="meta"> avec un <span class="source"> et un <span class="date">.recherche.css.Consignes :
.barre-recherche est un conteneur flex : l'<input> prend tout l'espace disponible, le <button> garde sa taille naturelle..layout est un conteneur flex : .filtres est à largeur fixe et ne rétrécit jamais, .resultats prend le reste..resultats est un conteneur flex avec flex-wrap: wrap : les .resultat font environ 45% de large..resultat est un conteneur flex en colonne. La .meta (source + date) est toujours collée en bas grâce à margin-top: auto..meta est un conteneur flex avec source à gauche et date à droite.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="recherche.css">
<title>Résultats de recherche</title>
</head>
<body>
<header class="header-recherche">
<div class="barre-recherche">
<input type="text" placeholder="Rechercher un article, un tutoriel..." value="Flexbox CSS">
<button>Rechercher</button>
</div>
</header>
<div class="layout">
<aside class="filtres">
<h2>Filtres</h2>
<div class="groupe-filtre">
<h3>Type</h3>
<label><input type="checkbox" checked> Tutoriel</label>
<label><input type="checkbox"> Vidéo</label>
<label><input type="checkbox"> Article</label>
</div>
<div class="groupe-filtre">
<h3>Niveau</h3>
<label><input type="checkbox" checked> Débutant</label>
<label><input type="checkbox" checked> Intermédiaire</label>
<label><input type="checkbox"> Avancé</label>
</div>
<div class="groupe-filtre">
<h3>Langue</h3>
<label><input type="checkbox" checked> Français</label>
<label><input type="checkbox"> Anglais</label>
<label><input type="checkbox"> Espagnol</label>
</div>
</aside>
<section class="resultats">
<article class="resultat">
<h2>Flexbox en pratique : construire une navbar responsive</h2>
<p class="description">Découvrez comment créer une barre de navigation moderne et responsive avec Flexbox. Nous couvrons justify-content, align-items et les patterns les plus courants en production.</p>
<div class="meta">
<span class="source">css-tricks.fr</span>
<span class="date">12 nov. 2024</span>
</div>
</article>
<article class="resultat">
<h2>Guide complet des propriétés Flexbox</h2>
<p class="description">Référence exhaustive de toutes les propriétés Flexbox avec des exemples interactifs. De flex-grow à align-self, tout ce que vous devez savoir.</p>
<div class="meta">
<span class="source">devdocs.io</span>
<span class="date">3 oct. 2024</span>
</div>
</article>
<article class="resultat">
<h2>Flexbox vs CSS Grid : quand utiliser lequel ?</h2>
<p class="description">Une comparaison détaillée des deux systèmes de mise en page modernes. Cas d'usage, avantages, limites et recommandations pour choisir le bon outil.</p>
<div class="meta">
<span class="source">alsacreations.com</span>
<span class="date">28 sept. 2024</span>
</div>
</article>
<article class="resultat">
<h2>Les pièges de Flexbox à éviter</h2>
<p class="description">Analyse des erreurs les plus fréquentes avec Flexbox : mauvaise direction des axes, confusion justify-content / align-items, oubli de flex-shrink sur les éléments fixes.</p>
<div class="meta">
<span class="source">grafikart.fr</span>
<span class="date">15 août 2024</span>
</div>
</article>
</section>
</div>
</body>
</html>
/* recherche.css — Correction exercice 23 */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: Arial, sans-serif;
background-color: #f0f4f8;
color: #2c2c2c;
}
/* Header avec barre de recherche */
.header-recherche {
background-color: #1A3A5C;
padding: 20px 30px;
}
/*
La barre de recherche est un conteneur flex.
L'input reçoit flex: 1 pour prendre tout l'espace disponible.
Le bouton garde sa taille naturelle (flex-grow: 0 par défaut).
*/
.barre-recherche {
display: flex;
max-width: 700px;
margin: 0 auto;
gap: 0;
}
.barre-recherche input {
flex: 1;
padding: 12px 16px;
font-size: 15px;
border: none;
border-radius: 4px 0 0 4px;
outline: none;
}
.barre-recherche button {
padding: 12px 24px;
background-color: #2E6DA4;
color: #ffffff;
border: none;
border-radius: 0 4px 4px 0;
font-size: 15px;
font-weight: bold;
cursor: pointer;
white-space: nowrap;
transition: background-color 0.2s ease;
}
.barre-recherche button:hover { background-color: #3a84c4; }
/* Layout principal */
.layout {
display: flex;
max-width: 1100px;
margin: 0 auto;
padding: 30px 20px;
gap: 30px;
}
/*
flex: 0 0 220px : la sidebar de filtres est fixe à 220px,
ne grandit pas, ne rétrécit pas.
*/
.filtres {
flex: 0 0 220px;
align-self: flex-start; /* Ne s'étire pas sur toute la hauteur du layout */
}
.filtres h2 {
color: #1A3A5C;
font-size: 16px;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #2E6DA4;
}
.groupe-filtre {
margin-bottom: 20px;
}
.groupe-filtre h3 {
font-size: 13px;
color: #888888;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 8px;
}
.groupe-filtre label {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: #444444;
padding: 4px 0;
cursor: pointer;
}
/* Résultats */
.resultats {
flex: 1; /* Prend tout l'espace restant après les filtres */
display: flex;
flex-wrap: wrap;
gap: 16px;
align-content: flex-start;
}
/*
Chaque résultat fait environ 45% pour tenir deux par ligne.
flex-direction: column pour empiler titre, description et meta.
La meta sera poussée en bas par margin-top: auto.
*/
.resultat {
flex: 1 1 45%;
display: flex;
flex-direction: column;
background-color: #ffffff;
border: 1px solid #dde3ed;
border-radius: 8px;
padding: 20px;
transition: box-shadow 0.2s ease;
}
.resultat:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
}
.resultat h2 {
font-size: 16px;
color: #1A3A5C;
margin-bottom: 10px;
line-height: 1.4;
}
.resultat h2:hover { text-decoration: underline; cursor: pointer; }
.description {
font-size: 13px;
color: #666666;
line-height: 1.7;
margin-bottom: 0;
}
/*
margin-top: auto pousse .meta en bas de la carte,
quelle que soit la longueur de la description.
.meta est elle-même un conteneur flex pour aligner
source à gauche et date à droite.
*/
.meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: auto;
padding-top: 14px;
border-top: 1px solid #f0f0f0;
margin-left: -20px;
margin-right: -20px;
padding-left: 20px;
padding-right: 20px;
}
.source {
font-size: 12px;
color: #2E6DA4;
font-weight: bold;
}
.date {
font-size: 12px;
color: #aaaaaa;
}
flex: 1 sur l'<input> de la barre de recherche : l'input absorbe tout l'espace disponible, laissant le bouton à sa taille naturelle. C'est le pattern barre de recherche par excellence — simple et robuste.
align-self: flex-start sur .filtres : sans cette propriété, les filtres s'étireraient sur toute la hauteur de .layout grâce à align-items: stretch (défaut). Avec flex-start, la sidebar garde sa hauteur naturelle et ne s'étire pas inutilement.
flex-direction: column + margin-top: auto sur .resultat : c'est le pattern bouton/meta collé en bas de carte. La carte s'étire verticalement pour avoir la même hauteur que sa voisine (grâce à stretch), et margin-top: auto sur .meta consomme tout l'espace vertical restant au-dessus d'elle.
align-content: flex-start sur .resultats : quand il y a plusieurs lignes (avec flex-wrap), align-content contrôle comment ces lignes sont distribuées verticalement. flex-start groupe les lignes en haut plutôt que de les étirer sur toute la hauteur — ce qui évite un espacement excessif entre les deux lignes de résultats.
Un site web n'est plus consulté depuis un seul type d'appareil. Concevoir une interface, c'est aujourd'hui concevoir pour une mosaïque d'écrans dont vous ne contrôlez ni la taille, ni la densité de pixels, ni les conditions d'utilisation. Le responsive design est la discipline qui consiste à faire en sorte qu'une même page HTML s'adapte correctement à tous ces contextes, sans qu'il soit nécessaire de maintenir plusieurs versions du site.
Les statistiques mondiales de navigation montrent une réalité tranchée : plus de la moitié du trafic web mondial provient aujourd'hui d'appareils mobiles. Cette proportion varie selon les secteurs — elle peut dépasser 80% pour les médias grand public et descendre sous 30% pour certains outils professionnels — mais la tendance de fond est structurelle et irréversible.
Concrètement, un même fichier HTML peut être affiché sur :
À cela s'ajoutent des variables que la largeur seule ne suffit pas à décrire :
L'orientation. Un même téléphone peut être tenu en portrait (étroit et long) ou en paysage (large et court). Les deux orientations produisent des expériences très différentes.
La densité de pixels. Les écrans modernes, notamment sur mobile, ont une densité très élevée (Retina, AMOLED). Une image de 100px CSS peut s'afficher sur 200 ou 300 pixels physiques. Une image raster non adaptée apparaîtra floue sur ces écrans.
Les capacités d'interaction. Un écran tactile n'a pas de curseur hoverable. Les cibles interactives (boutons, liens) doivent être dimensionnées pour le doigt, soit au minimum 44px de hauteur, et non pour un pointeur de souris précis au pixel.
La luminosité ambiante et la lisibilité. Un site consulté en plein soleil sur mobile impose des contrastes élevés. Un site consulté la nuit dans l'obscurité peut bénéficier d'un mode sombre.
Le responsive design n'est pas un gadget. Un site non responsive est pénalisé par les moteurs de recherche depuis 2015 (Google Mobile-First Indexing). Il est inutilisable pour une fraction croissante de vos utilisateurs. Et il signale un manque de soin professionnel. Aujourd'hui, la capacité d'adaptation aux écrans est une exigence de base, pas une option.
Rendre un site responsive ne se résume pas à "rétrécir la mise en page". Cela implique de résoudre plusieurs contraintes techniques qui se manifestent différemment selon les écrans.
Contrainte 1 : la largeur disponible est inconnue et variable.
Contrairement à une mise en page imprimée où le format papier est fixe, une page web peut être affichée dans n'importe quelle largeur. Une mise en page rigide en pixels fixes est condamnée à être soit trop large (scroll horizontal sur mobile), soit trop étroite (espace gâché sur grand écran). La réponse technique est l'utilisation d'unités relatives (%, em, rem, vw) et de contraintes souples (max-width, min-width) plutôt que de valeurs absolues.
Contrainte 2 : la quantité de contenu peut déborder. Sur un écran large, une navigation horizontale avec dix liens tient sur une ligne. Sur mobile, ces mêmes liens débordent. Un tableau de données à douze colonnes est lisible sur desktop, illisible sur un écran de 375px. Le responsive design impose de repenser l'organisation du contenu, pas seulement sa taille.
Contrainte 3 : les performances réseau. Un utilisateur mobile sur une connexion 4G — ou pire, 3G — n'a pas les mêmes capacités de chargement qu'un utilisateur sur fibre optique. Charger une image de 4 Mo sur mobile est inacceptable. Le responsive design s'accompagne donc d'une réflexion sur les images adaptatives, les polices optimisées et la réduction du poids des ressources selon le contexte.
Contrainte 4 : le viewport et son comportement par défaut.
Sans instruction spécifique, les navigateurs mobiles appliquent un comportement hérité : ils affichent la page comme si elle avait environ 980px de large, puis la rétrécissent pour qu'elle tienne dans l'écran — rendant le texte illisible et les boutons inatteignables. Pour désactiver ce comportement et indiquer au navigateur que la page est conçue pour mobile, la balise <meta name="viewport"> est indispensable.
<!-- Cette balise doit être présente dans le <head> de toutes vos pages -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--
width=device-width : la largeur de la page correspond à la largeur réelle
de l'écran, en pixels CSS (et non en pixels physiques).
initial-scale=1 : pas de zoom appliqué au chargement de la page.
Sans cette balise, vos media queries ne fonctionneront pas correctement
sur mobile — c'est l'erreur de débutant numéro un en responsive design.
-->
Contrainte 5 : la complexité de maintenance. Une approche naïve du responsive design consiste à écrire une mise en page desktop complète, puis à la "réparer" pour chaque taille d'écran en ajoutant des media queries. Cette approche produit un CSS difficile à maintenir, truffé d'annulations de propriétés. L'alternative est d'adopter une stratégie de conception cohérente dès le départ — mobile-first ou desktop-first — que nous allons détailler dans la section 8.2.
Il existe deux stratégies opposées pour aborder le responsive design. Le choix entre les deux n'est pas anodin : il détermine la structure de votre CSS, l'ordre dans lequel vous écrivez vos règles, et la facilité de maintenance à long terme. Connaître les deux vous permettra de choisir la bonne approche selon le contexte du projet, et de lire et comprendre le code d'autrui quelle que soit la stratégie adoptée.
L'approche mobile-first consiste à écrire en premier le CSS pour les petits écrans — le mobile — puis à ajouter des règles pour les écrans plus larges à mesure que la largeur augmente. En pratique, cela signifie que votre CSS de base, sans aucune media query, est déjà fonctionnel et soigné sur mobile. Les media queries ne servent qu'à enrichir la mise en page pour les écrans plus grands.
/* Approche mobile-first : le CSS de base cible le mobile */
.grille {
/* Sur mobile : les cartes s'empilent en une seule colonne */
display: flex;
flex-direction: column;
gap: 16px;
}
/* À partir de 768px (tablette) : deux colonnes */
@media (min-width: 768px) {
.grille {
flex-direction: row;
flex-wrap: wrap;
}
.carte {
flex: 1 1 45%;
}
}
/* À partir de 1024px (desktop) : trois colonnes */
@media (min-width: 1024px) {
.carte {
flex: 1 1 30%;
}
}
Les media queries mobile-first utilisent min-width : "à partir de cette largeur, applique ces règles supplémentaires". L'ensemble des styles de base reste actif et n'est jamais annulé — on ne fait qu'ajouter.
Premièrement, elle force à prioriser le contenu. Sur un écran de 375px, vous ne pouvez pas tout afficher. Vous devez décider ce qui est essentiel. Cette contrainte est saine : elle produit des interfaces plus claires et mieux hiérarchisées, y compris sur desktop.
Deuxièmement, elle produit un CSS plus léger sur mobile. Un appareil mobile ne charge que le CSS de base, sans devoir parser et ignorer des règles desktop inutiles. C'est un gain de performance modeste mais réel.
Troisièmement, elle s'aligne avec la manière dont Google indexe les pages depuis 2019 (Mobile-First Indexing). Google analyse en priorité la version mobile de votre site. Si cette version est négligée, le référencement en pâtit.
Quatrièmement, elle produit un CSS plus propre. Ajouter des propriétés est plus simple qu'en annuler. En mobile-first, vous ne passez pas votre temps à écrire display: block pour annuler un display: flex défini pour desktop.
Mobile-first ne signifie pas "concevoir uniquement pour mobile". Cela signifie commencer par le mobile et enrichir progressivement. Le résultat final sur desktop est aussi soigné — souvent davantage, car la réflexion sur le contenu essentiel a été faite en amont.
L'approche desktop-first est l'inverse : vous écrivez d'abord le CSS pour les grands écrans, puis vous ajoutez des règles pour adapter la mise en page aux écrans plus petits. Les media queries utilisent max-width : "jusqu'à cette largeur, applique ces règles qui annulent ou remplacent le comportement desktop".
/* Approche desktop-first : le CSS de base cible le desktop */
.grille {
/* Sur desktop : trois colonnes côte à côte */
display: flex;
gap: 20px;
}
.carte {
flex: 1;
}
/* En dessous de 1024px (tablette) : deux colonnes */
@media (max-width: 1024px) {
.grille {
flex-wrap: wrap;
}
.carte {
flex: 1 1 45%;
}
}
/* En dessous de 768px (mobile) : une seule colonne */
@media (max-width: 768px) {
.grille {
flex-direction: column;
}
}
Pour des projets dont l'audience est exclusivement ou massivement desktop — outils de gestion interne, logiciels de comptabilité, interfaces d'administration complexes — le desktop-first est une approche pragmatique. Si vos utilisateurs n'accèdent jamais au produit sur mobile, optimiser d'abord pour desktop est une allocation de temps raisonnable.
Le desktop-first est aussi courant dans les projets existants. Si vous reprenez un codebase écrit en desktop-first depuis des années, le convertir en mobile-first représente une réécriture massive. Dans ce cas, continuer en desktop-first et améliorer progressivement la version mobile est souvent plus sage.
Les pièges du desktop-first.
Le principal problème est la tendance à accumuler des annulations. Une règle définie pour desktop doit être explicitement annulée pour mobile. Sur une base de code importante, cela produit des cascades d'annulations difficiles à suivre :
/* Desktop-first : on annule des propriétés en cascade */
.sidebar {
display: block;
width: 280px;
float: left;
}
@media (max-width: 768px) {
.sidebar {
display: none; /* Annulation */
width: auto; /* Annulation */
float: none; /* Annulation */
}
}
/* En mobile-first, .sidebar n'existerait pas par défaut sur mobile,
et on l'ajouterait seulement à partir du breakpoint desktop. */
|
Media query utilisée |
|
|
|
CSS de base cible |
Mobile |
Desktop |
|
Direction |
On ajoute en élargissant |
On annule en rétrécissant |
|
Recommandé pour |
La majorité des projets web |
Outils internes, apps desktop |
|
Aligne avec Google |
Oui |
Non |
|
Risque principal |
Oublier de soigner le desktop |
Accumuler des annulations |
En pratique, les deux approches peuvent coexister dans un même projet. On peut écrire le layout global en mobile-first et certains composants complexes en desktop-first si leur conception part naturellement du grand écran. L'important est d'être cohérent à l'échelle d'un composant, et d'en documenter le choix dans les commentaires.
Objectif : construire une page de tableau de bord en approche mobile-first, sans media query. Le CSS de base doit être fonctionnel et lisible à 375px de large. Cet exercice travaille la discipline de penser le CSS à partir du plus petit écran : quelles largeurs deviennent relatives, quels éléments s'empilent par défaut, qu'est-ce qui est vraiment nécessaire sur mobile.
Consignes HTML :
Créez un fichier mobile-first.html avec la balise <meta name="viewport" content="width=device-width, initial-scale=1"> dans le <head>.
Créez un <header> contenant une <div class="navbar"> avec un <span class="logo"> (texte : DashApp) et une <nav> avec quatre <a> : Accueil, Rapports, Équipe, Paramètres.
Créez un <div class="page"> contenant :
Un <main class="contenu"> avec un <h1> (Tableau de bord), deux <p> de texte, et une <div class="stats"> contenant trois <div class="stat"> — chacun avec un <span class="stat-valeur"> (un nombre) et un <span class="stat-label"> (un libellé).
Un <aside class="sidebar"> avec un <h2> et trois <p> de texte secondaire.
Créez un <footer> avec un <p> de copyright.
Liez un fichier mobile-first.css.
Consignes CSS :
Réfléchissez à chaque décision comme si vous étiez sur un écran de 375px. Aucune media query dans ce fichier.
Conteneurs principaux (.navbar, .page) : ils ne doivent jamais dépasser une certaine largeur sur les grands écrans, mais doivent se rétrécir librement sur les petits. Comment définir une largeur qui se comporte ainsi ? Comment les centrer sur la page ?
Mise en page de .page : sur mobile, le contenu principal et la sidebar doivent apparaître l'un sous l'autre dans un ordre logique pour un lecteur mobile — le contenu d'abord, la sidebar ensuite. Quelle est la technique la plus simple pour obtenir cet empilement sans écrire de propriété de positionnement ? Pourquoi serait-il une erreur d'utiliser Flexbox ici dans le CSS de base ?
La navbar (.navbar) : le logo doit rester visible et ne jamais rétrécir. Les liens de navigation peuvent se comprimer si l'espace manque, mais ne doivent pas créer de scroll horizontal. Comment garantir ces deux comportements ?
Les statistiques (.stats et .stat) : les trois blocs de statistiques doivent se partager l'espace disponible équitablement, quelle que soit la largeur de l'écran. Si l'espace devient vraiment insuffisant, ils doivent pouvoir passer à la ligne. Comment définir leur taille pour qu'elle soit à la fois flexible et dotée d'un minimum raisonnable ?
Typographie : les tailles de police ne doivent pas être figées en pixels. Quelle unité utiliseriez-vous pour respecter les préférences d'accessibilité de l'utilisateur tout en gardant des proportions cohérentes ?
Vérification finale : ouvrez les DevTools de votre navigateur, passez en mode mobile et réglez la largeur à 375px. Aucun élément ne doit dépasser la largeur de l'écran et créer un scroll horizontal. Si c'est le cas, cherchez quel élément a une largeur trop rigide.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="mobile-first.css">
<title>Tableau de bord — Mobile First</title>
</head>
<body>
<header>
<div class="navbar">
<span class="logo">DashApp</span>
<nav>
<a href="#">Accueil</a>
<a href="#">Rapports</a>
<a href="#">Équipe</a>
<a href="#">Paramètres</a>
</nav>
</div>
</header>
<div class="page">
<main class="contenu">
<h1>Tableau de bord</h1>
<p>Voici les indicateurs clés de performance pour le mois en cours. Ces données sont mises à jour quotidiennement à partir de nos sources analytiques.</p>
<p>Retrouvez le détail de chaque indicateur dans les rapports hebdomadaires disponibles dans votre espace personnel.</p>
<div class="stats">
<div class="stat">
<span class="stat-valeur">12 480</span>
<span class="stat-label">Visiteurs</span>
</div>
<div class="stat">
<span class="stat-valeur">847</span>
<span class="stat-label">Conversions</span>
</div>
<div class="stat">
<span class="stat-valeur">6,8 %</span>
<span class="stat-label">Taux</span>
</div>
</div>
</main>
<aside class="sidebar">
<h2>Infos</h2>
<p>Dernière mise à jour : 02 mars 2024.</p>
<p>Rapport mensuel disponible en téléchargement depuis la section Documents.</p>
<p>Support disponible du lundi au vendredi, de 9h à 18h.</p>
</aside>
</div>
<footer>
<p>© 2024 DashApp. Tous droits réservés.</p>
</footer>
</body>
</html>
/* mobile-first.css — Correction exercice 25
CSS de base : mobile en premier, sans aucune media query.
Ce fichier sera enrichi de media queries dans les exercices suivants. */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
font-size: 1rem; /* Unité relative : respecte les préférences système */
background-color: #f0f4f8;
color: #2c2c2c;
}
/* --- HEADER --- */
header {
background-color: #1A3A5C;
}
/*
max-width + margin: 0 auto : la navbar ne dépasse jamais 960px,
mais se rétrécit librement en dessous — elle ne déborde jamais.
*/
.navbar {
max-width: 960px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 16px;
height: 56px;
}
.logo {
color: #ffffff;
font-size: 1.125rem;
font-weight: bold;
flex-shrink: 0; /* Le logo ne rétrécit jamais */
margin-right: 12px;
}
.navbar nav {
display: flex;
gap: 2px;
overflow: hidden; /* Les liens qui dépassent sont masqués, pas de scroll */
}
.navbar nav a {
display: block;
padding: 6px 10px;
color: #a8c4e0;
text-decoration: none;
font-size: 0.875rem;
border-radius: 4px;
white-space: nowrap;
transition: background-color 0.2s ease, color 0.2s ease;
}
.navbar nav a:hover {
background-color: #2E6DA4;
color: #ffffff;
}
/* --- LAYOUT PRINCIPAL --- */
.page {
max-width: 960px;
margin: 0 auto;
padding: 20px 16px;
/*
Pas de display: flex ici.
Sur mobile, .contenu et .sidebar s'empilent naturellement
dans le flux normal — c'est exactement ce qu'on veut.
Ajouter display: flex avec flex-direction: column serait
du desktop-first déguisé : on partirait d'un état flex
qu'on modifierait ensuite. Le flux normal fait le travail
gratuitement. On ajoutera display: flex en media query (section 8.3).
*/
}
/* --- CONTENU PRINCIPAL --- */
.contenu {
background-color: #ffffff;
padding: 20px 16px;
border: 1px solid #dde3ed;
margin-bottom: 16px;
}
.contenu h1 {
color: #1A3A5C;
font-size: 1.375rem;
margin-bottom: 14px;
}
.contenu p {
font-size: 0.9375rem;
line-height: 1.7;
color: #555;
margin-bottom: 12px;
}
/* --- STATS --- */
.stats {
display: flex;
gap: 10px;
margin-top: 20px;
flex-wrap: wrap; /* Les stats passent à la ligne si l'espace manque */
}
/*
flex: 1 1 80px : chaque stat grandit équitablement
et descend au minimum à 80px avant de passer à la ligne.
*/
.stat {
flex: 1 1 80px;
display: flex;
flex-direction: column;
align-items: center;
background-color: #e8f0fe;
border: 1px solid #c8daf0;
padding: 14px 10px;
border-radius: 8px;
}
.stat-valeur {
font-size: 1.25rem;
font-weight: bold;
color: #1A3A5C;
}
.stat-label {
font-size: 0.75rem;
color: #666;
margin-top: 4px;
text-align: center;
}
/* --- SIDEBAR --- */
.sidebar {
background-color: #ffffff;
border: 1px solid #dde3ed;
padding: 20px 16px;
}
.sidebar h2 {
color: #1A3A5C;
font-size: 1rem;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 2px solid #2E6DA4;
}
.sidebar p {
font-size: 0.875rem;
color: #555;
line-height: 1.6;
margin-bottom: 10px;
}
.sidebar p:last-child {
margin-bottom: 0;
}
/* --- FOOTER --- */
footer {
background-color: #1A3A5C;
color: #a8c4e0;
text-align: center;
padding: 16px;
font-size: 0.8125rem;
margin-top: 16px;
}
max-width au lieu d'une largeur fixe : max-width: 960px avec margin: 0 auto dit : "ne dépasse pas 960px, mais rétrécis librement en dessous". Le conteneur s'adapte naturellement à n'importe quelle largeur d'écran. Une largeur fixe en pixels déborde ; une max-width s'adapte.
Pas de Flexbox sur .page dans le CSS de base : sur mobile, .contenu et .sidebar sont des blocs qui s'empilent dans le flux normal — c'est exactement le comportement voulu. Utiliser display: flex avec flex-direction: column pour "simuler" cet empilement serait du desktop-first déguisé : on partirait d'un état flex qu'on devrait modifier en media query. En mobile-first pur, le flux normal gère l'empilement gratuitement, et Flexbox n'est introduit qu'en media query pour les écrans larges.
font-size: 1rem : contrairement à 16px fixe, 1rem respecte les préférences d'accessibilité de l'utilisateur. Un utilisateur qui a configuré une police plus grande dans son navigateur verra le texte s'adapter correctement.
flex-shrink: 0 sur .logo : sans cette déclaration, Flexbox pourrait comprimer le logo si la navbar manque d'espace. On garantit ainsi que le logo reste toujours entièrement visible, quelle que soit la largeur de l'écran.
Les media queries sont le mécanisme CSS qui permet d'appliquer des règles conditionnellement, en fonction des caractéristiques de l'environnement d'affichage. C'est l'outil central du responsive design : sans media queries, votre CSS s'applique de manière identique sur tous les écrans. Avec elles, vous pouvez adapter précisément la mise en page, la typographie et l'organisation du contenu selon le contexte.
Une media query s'écrit avec le mot-clé @media, suivi d'une condition entre parenthèses, puis d'un bloc de règles CSS entre accolades. Ces règles ne s'appliquent que si la condition est vérifiée.
@media (condition) {
/* Ces règles s'appliquent uniquement si la condition est vraie */
sélecteur {
propriété: valeur;
}
}
Voici la forme la plus courante, que vous utiliserez dans la quasi-totalité de vos projets :
/* S'applique à partir de 768px de large — approche mobile-first */
@media (min-width: 768px) {
.page {
display: flex;
gap: 24px;
}
}
/* S'applique jusqu'à 768px de large — approche desktop-first */
@media (max-width: 768px) {
.sidebar {
display: none;
}
}
Placement dans le fichier CSS.
Les media queries se placent généralement après les règles qu'elles enrichissent ou modifient. En mobile-first, le fichier CSS suit une logique progressive : les règles de base en premier, puis les media queries pour les écrans plus larges, dans l'ordre croissant des breakpoints.
/* 1. Règles de base — s'appliquent à tous les écrans */
.nav {
flex-direction: column;
}
/* 2. Enrichissement à partir de 600px */
@media (min-width: 600px) {
.nav {
flex-direction: row;
}
}
/* 3. Enrichissement supplémentaire à partir de 1024px */
@media (min-width: 1024px) {
.nav {
gap: 30px;
}
}
Combiner plusieurs conditions.
On peut combiner plusieurs conditions avec and pour qu'une règle ne s'applique que dans une plage de largeurs précise.
/* S'applique uniquement entre 600px et 1023px — format tablette uniquement */
@media (min-width: 600px) and (max-width: 1023px) {
.grille {
flex-wrap: wrap;
}
}
On peut également cibler plusieurs conditions indépendantes avec une virgule, qui joue le rôle d'un ou :
/* S'applique en dessous de 480px OU au-dessus de 1400px */
@media (max-width: 480px), (min-width: 1400px) {
.encart {
display: none;
}
}
Cibler le type de média.
Avant min-width et max-width, les media queries ciblaient le type de support : screen pour les écrans, print pour l'impression, speech pour les synthèses vocales. Cette syntaxe reste utile, en particulier pour les feuilles de style d'impression.
/* Règles appliquées uniquement à l'impression */
@media print {
.navbar,
.sidebar,
footer {
display: none;
}
body {
font-size: 12pt;
color: #000000;
background: none;
}
}
En pratique, omettre le type de média (screen) est parfaitement valide et très courant. La quasi-totalité du code responsive que vous écrirez n'utilisera que min-width et max-width, sans préciser le type.
Au-delà de min-width et max-width, plusieurs autres conditions sont régulièrement utiles en production.
orientation.Permet de différencier portrait (hauteur > largeur) et paysage (largeur > hauteur). Particulièrement utile sur tablette.
@media (orientation: landscape) and (min-width: 768px) {
.galerie {
flex-direction: row;
}
}
prefers-color-scheme.Détecte si l'utilisateur a activé le mode sombre dans ses préférences système. C'est aujourd'hui une attente courante des utilisateurs, et CSS permet d'y répondre sans JavaScript.
<!-- prefers-color-scheme.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="prefers-color-scheme.css">
<title>Mode sombre</title>
</head>
<body>
<div class="carte">
<h2>Titre de la carte</h2>
<p>Ce contenu s'adapte automatiquement au mode sombre ou clair défini dans les préférences système de l'utilisateur.</p>
</div>
</body>
</html>
/* prefers-color-scheme.css */
body {
background-color: #f0f4f8;
color: #2c2c2c;
font-family: Arial, sans-serif;
padding: 40px;
}
.carte {
background-color: #ffffff;
border: 1px solid #dde3ed;
border-radius: 8px;
padding: 24px;
max-width: 400px;
margin: 0 auto;
}
.carte h2 { color: #1A3A5C; margin-bottom: 10px; }
.carte p { color: #555; line-height: 1.7; font-size: 14px; }
@media (prefers-color-scheme: dark) {
body {
background-color: #0f1923;
color: #e0e6ef;
}
.carte {
background-color: #1a2a3a;
border-color: #2e4460;
}
.carte h2 { color: #a8c4e0; }
.carte p { color: #9aafc4; }
}
prefers-reduced-motion.Certains utilisateurs souffrent de troubles vestibulaires ou d'épilepsie photosensible. Cette condition détecte leur préférence système pour supprimer les animations.
.bouton {
transition: background-color 0.3s ease, transform 0.2s ease;
}
.bouton:hover {
transform: scale(1.05);
}
@media (prefers-reduced-motion: reduce) {
.bouton {
transition: none;
}
.bouton:hover {
transform: none;
}
}
hover et pointer.Ces conditions détectent les capacités d'interaction de l'appareil. Un écran tactile n'a pas de curseur — les effets :hover peuvent s'y déclencher de façon inattendue.
/* Les effets hover ne s'appliquent que si l'appareil a une souris */
@media (hover: hover) and (pointer: fine) {
.carte:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
}
/* Sur les appareils tactiles, les cibles doivent être plus grandes */
@media (pointer: coarse) {
.btn {
min-height: 44px;
padding: 12px 20px;
}
}
|
|
Enrichir la mise en page en élargissant (mobile-first) |
|
|
Adapter la mise en page en rétrécissant (desktop-first) |
|
|
Adapter selon l'orientation de la tablette |
|
|
Proposer un thème sombre automatique |
|
|
Supprimer les animations pour l'accessibilité |
|
|
Réserver les effets hover aux appareils avec souris |
|
|
Agrandir les cibles tactiles sur mobile |
|
|
Adapter la mise en page à l'impression |
Objectif : construire une page de profil de développeur, comme on en trouve sur GitHub ou dans un portfolio personnel. Sur mobile, tout s'empile en une colonne centrée. Sur tablette, le profil s'organise en deux zones côte à côte. Sur desktop, la mise en page s'élargit et s'aère, avec une grille de compétences sur plusieurs colonnes. En bonus, la page supporte le mode sombre automatiquement.
C'est un composant que vous réutiliserez dans vos propres projets : carte de profil, page "À propos", présentation d'équipe.
Consignes HTML :
profil-dev.html avec la balise <meta name="viewport">.<div class="page"> contenant une unique <div class="profil">..profil, créez deux zones :
<div class="profil-gauche"> contenant :
<div class="avatar"> avec vos initiales.<h1> avec un nom.<p class="titre-poste"> (ex : Développeur Front-End).<p class="bio"> (deux phrases de présentation).<div class="reseaux"> avec trois <a> : GitHub, LinkedIn, Portfolio.<div class="profil-droite"> contenant :
<div class="section"> avec un <h2> (Compétences) et une <div class="competences"> contenant au moins huit <span class="tag"> (CSS, HTML, JavaScript, Flexbox, Git, React, Figma, Responsive).<div class="section"> avec un <h2> (Projets récents) et deux <div class="projet">, chacun avec un <h3>, un <p> de description et un <span class="statut"> (En cours ou Terminé).profil-dev.css.Consignes CSS :
Pensez à chaque écran avant d'écrire la moindre ligne. Que voit un utilisateur sur son téléphone ? Que voit-il en ouvrant la page sur son ordinateur ?
CSS de base (mobile) : la page est centrée avec une largeur maximale. .profil est une colonne unique — .profil-gauche et .profil-droite s'empilent. L'avatar est un grand cercle centré avec vos initiales dedans. Les .tag de compétences se répartissent naturellement en ligne et passent à la ligne si besoin. Chaque .projet a un fond, du padding et un aspect de carte.
À partir de 768px : .profil passe en deux colonnes côte à côte — .profil-gauche occupe environ un tiers de la largeur et ne rétrécit pas, .profil-droite prend le reste. La colonne gauche reste collée en haut.
À partir de 1100px : la page s'aère davantage. La .profil-gauche s'élargit légèrement. Les .competences s'organisent pour afficher les tags sur davantage de colonnes. Les .projet passent côte à côte.
Mode sombre : adaptez au minimum le fond de la page, le fond du profil, les couleurs de texte et les tags. Le résultat doit être lisible et cohérent.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="profil-dev.css">
<title>Profil développeur</title>
</head>
<body>
<div class="page">
<div class="profil">
<div class="profil-gauche">
<div class="avatar">AL</div>
<h1>Alice Laurent</h1>
<p class="titre-poste">Développeuse Front-End</p>
<p class="bio">Passionnée par le CSS, l'accessibilité et les interfaces soignées. Je transforme les maquettes en expériences web fluides et performantes.</p>
<div class="reseaux">
<a href="#">GitHub</a>
<a href="#">LinkedIn</a>
<a href="#">Portfolio</a>
</div>
</div>
<div class="profil-droite">
<div class="section">
<h2>Compétences</h2>
<div class="competences">
<span class="tag">HTML</span>
<span class="tag">CSS</span>
<span class="tag">JavaScript</span>
<span class="tag">Flexbox</span>
<span class="tag">CSS Grid</span>
<span class="tag">React</span>
<span class="tag">Git</span>
<span class="tag">Figma</span>
<span class="tag">Responsive</span>
<span class="tag">Accessibilité</span>
</div>
</div>
<div class="section">
<h2>Projets récents</h2>
<div class="projet">
<h3>Refonte du site vitrine</h3>
<p>Migration d'un site desktop-only vers une expérience responsive mobile-first. Gain de 40% sur le Core Web Vitals mobile.</p>
<span class="statut statut-termine">Terminé</span>
</div>
<div class="projet">
<h3>Composant de design system</h3>
<p>Création d'une bibliothèque de composants réutilisables en HTML/CSS pur, documentée et accessible.</p>
<span class="statut statut-en-cours">En cours</span>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
/* profil-dev.css */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* =====================
CSS DE BASE — MOBILE
===================== */
body {
font-family: Arial, sans-serif;
font-size: 1rem;
background-color: #f0f4f8;
color: #2c2c2c;
min-height: 100vh;
}
.page {
max-width: 1100px;
margin: 0 auto;
padding: 24px 16px;
}
/* Sur mobile : .profil est une colonne unique */
.profil {
background-color: #ffffff;
border-radius: 12px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
overflow: hidden;
}
/* --- COLONNE GAUCHE --- */
.profil-gauche {
background-color: #1A3A5C;
padding: 32px 24px;
text-align: center;
}
.avatar {
width: 90px;
height: 90px;
border-radius: 50%;
background-color: #2E6DA4;
color: #ffffff;
font-size: 1.75rem;
font-weight: bold;
line-height: 90px;
text-align: center;
margin: 0 auto 20px;
border: 3px solid rgba(255, 255, 255, 0.2);
}
.profil-gauche h1 {
color: #ffffff;
font-size: 1.375rem;
margin-bottom: 6px;
}
.titre-poste {
color: #a8c4e0;
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 16px;
}
.bio {
color: #c8daf0;
font-size: 0.875rem;
line-height: 1.7;
margin-bottom: 24px;
}
.reseaux {
display: flex;
justify-content: center;
gap: 10px;
flex-wrap: wrap;
}
.reseaux a {
display: block;
padding: 7px 16px;
background-color: rgba(255, 255, 255, 0.1);
color: #a8c4e0;
text-decoration: none;
border-radius: 20px;
font-size: 0.8125rem;
border: 1px solid rgba(255, 255, 255, 0.15);
transition: background-color 0.2s ease, color 0.2s ease;
}
.reseaux a:hover {
background-color: #2E6DA4;
color: #ffffff;
}
/* --- COLONNE DROITE --- */
.profil-droite {
padding: 28px 24px;
}
.section {
margin-bottom: 32px;
}
.section:last-child {
margin-bottom: 0;
}
.section h2 {
color: #1A3A5C;
font-size: 1rem;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 14px;
padding-bottom: 8px;
border-bottom: 2px solid #e8f0fe;
}
/* Tags de compétences — inline naturel, passent à la ligne si besoin */
.competences {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.tag {
display: inline-block;
background-color: #e8f0fe;
color: #2E6DA4;
font-size: 0.8125rem;
font-weight: bold;
padding: 5px 12px;
border-radius: 20px;
}
/* Cartes de projets */
.projet {
background-color: #f8fafd;
border: 1px solid #dde3ed;
border-radius: 8px;
padding: 18px;
margin-bottom: 12px;
position: relative;
}
.projet:last-child {
margin-bottom: 0;
}
.projet h3 {
color: #1A3A5C;
font-size: 0.9375rem;
margin-bottom: 8px;
}
.projet p {
color: #666;
font-size: 0.8125rem;
line-height: 1.6;
margin-bottom: 12px;
}
.statut {
display: inline-block;
font-size: 0.75rem;
font-weight: bold;
padding: 3px 10px;
border-radius: 10px;
}
.statut-termine {
background-color: #e6f4ea;
color: #2d7a3a;
}
.statut-en-cours {
background-color: #fff4e0;
color: #b06a00;
}
/* =========================
À PARTIR DE 768px — TABLETTE
========================= */
@media (min-width: 768px) {
.page {
padding: 40px 24px;
}
/*
.profil passe en deux colonnes :
.profil-gauche à largeur fixe, .profil-droite prend le reste.
align-items: stretch étire les deux colonnes à la même hauteur.
*/
.profil {
display: flex;
align-items: stretch;
}
.profil-gauche {
flex: 0 0 260px;
text-align: left;
}
.avatar {
margin: 0 0 20px;
}
.reseaux {
justify-content: flex-start;
}
.profil-droite {
flex: 1;
padding: 32px;
}
}
/* =========================
À PARTIR DE 1100px — DESKTOP
========================= */
@media (min-width: 1100px) {
.page {
padding: 60px 24px;
}
.profil-gauche {
flex: 0 0 300px;
padding: 40px 32px;
}
.avatar {
width: 110px;
height: 110px;
line-height: 110px;
font-size: 2rem;
}
.profil-gauche h1 {
font-size: 1.625rem;
}
.profil-droite {
padding: 40px;
}
/* Les projets passent côte à côte sur desktop */
.section:last-child .projet {
display: inline-block;
width: calc(50% - 6px);
vertical-align: top;
margin-bottom: 0;
}
.section:last-child .projet:first-of-type {
margin-right: 12px;
}
}
/* =============
MODE SOMBRE
============= */
@media (prefers-color-scheme: dark) {
body {
background-color: #0c1520;
color: #d0dce8;
}
.profil {
background-color: #132030;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
}
.profil-gauche {
background-color: #0a1828;
}
.profil-droite {
background-color: #132030;
}
.section h2 {
color: #a8c4e0;
border-bottom-color: #1e3a56;
}
.tag {
background-color: #1a3a56;
color: #7ab0d8;
}
.projet {
background-color: #0f2030;
border-color: #1e3a56;
}
.projet h3 {
color: #a8c4e0;
}
.projet p {
color: #7a9bbf;
}
.statut-termine {
background-color: #0f2a18;
color: #5aba72;
}
.statut-en-cours {
background-color: #2a1e00;
color: #d4900a;
}
}
display: flex sur .profil n'apparaît qu'à 768px : sur mobile, .profil-gauche et .profil-droite s'empilent dans le flux normal — aucune propriété de mise en page n'est nécessaire. Flexbox est introduit uniquement quand l'espace permet une mise en page en colonnes. C'est la discipline mobile-first appliquée strictement.
align-items: stretch sur .profil (valeur par défaut) : les deux colonnes ont la même hauteur automatiquement, même si .profil-gauche contient moins de contenu que .profil-droite. Le fond sombre de la colonne gauche s'étend jusqu'en bas, ce qui donne l'effet de barre latérale colorée sans aucun hack.
flex: 0 0 300px sur .profil-gauche : la colonne gauche ne grandit pas, ne rétrécit pas et fait toujours 300px. C'est le pattern sidebar fixe vu en 7.5, appliqué ici dans un contexte responsive : la valeur passe de 260px à 300px entre 768px et 1100px grâce aux deux media queries.
Les projets côte à côte avec inline-block à 1100px : on aurait pu utiliser Flexbox sur le conteneur de projets, mais cela aurait nécessité de modifier la structure HTML. Ici, inline-block avec calc(50% - 6px) suffit pour deux cartes côte à côte, sans toucher au HTML. C'est une solution légère et ciblée pour un besoin précis.
Un point de rupture — ou breakpoint — est la largeur à laquelle votre mise en page change de comportement. C'est la valeur que vous écrivez dans vos @media (min-width: ???px). Bien choisir ces valeurs est une décision de conception à part entière : de mauvais breakpoints produisent des mises en page qui se cassent entre deux tailles d'écran, ou qui ignorent les appareils réels de vos utilisateurs.
Les breakpoints de référence : Bootstrap.
Plutôt que d'inventer des valeurs arbitraires, il est judicieux de s'appuyer sur un système éprouvé. Bootstrap, le framework CSS le plus utilisé au monde, a défini depuis sa version 5 un jeu de six breakpoints qui couvrent la quasi-totalité des appareils du marché. Ces valeurs sont aujourd'hui des références dans l'industrie, y compris pour les projets qui n'utilisent pas Bootstrap.
| Nom | Valeur min-width
|
Contexte typique |
|---|---|---|
xs — Extra small |
Aucune (CSS de base) | Smartphones compacts, < 576px |
sm — Small |
576px |
Grands smartphones en paysage |
md — Medium |
768px |
Tablettes |
lg — Large |
992px |
Ordinateurs portables |
xl — Extra large |
1200px |
Desktops |
xxl — Extra extra large |
1400px |
Grands écrans, moniteurs larges |
En mobile-first, le CSS de base (sans media query) cible le palier xs. Chaque media query min-width fait ensuite monter la mise en page d'un palier.
/* xs — CSS de base, aucune media query */
.conteneur {
padding: 0 16px;
}
/* sm — à partir de 576px */
@media (min-width: 576px) {
.conteneur {
padding: 0 24px;
}
}
/* md — à partir de 768px */
@media (min-width: 768px) {
.conteneur {
max-width: 720px;
margin: 0 auto;
}
}
/* lg — à partir de 992px */
@media (min-width: 992px) {
.conteneur {
max-width: 960px;
}
}
/* xl — à partir de 1200px */
@media (min-width: 1200px) {
.conteneur {
max-width: 1140px;
}
}
/* xxl — à partir de 1400px */
@media (min-width: 1400px) {
.conteneur {
max-width: 1320px;
}
}
Non. Ces six valeurs sont un référentiel, pas une obligation. La majorité des projets n'en utilisent que deux ou trois. L'intérêt est de partir d'une base cohérente et reconnue plutôt que d'inventer des valeurs de toutes pièces. Choisissez les paliers dont votre contenu a besoin, et ignorez les autres.
/*
Projet simple : on n'utilise que md et lg.
La mise en page n'a besoin de changer qu'à ces deux moments.
*/
/* xs/sm — CSS de base */
.grille { flex-direction: column; }
/* md — tablette : deux colonnes */
@media (min-width: 768px) {
.grille { flex-direction: row; flex-wrap: wrap; }
.col { flex: 1 1 calc(50% - 10px); }
}
/* lg — desktop : trois colonnes */
@media (min-width: 992px) {
.col { flex: 1 1 calc(33.333% - 14px); }
}
Les breakpoints Bootstrap sont un point de départ. Rien n'empêche d'ajouter un breakpoint intermédiaire si votre contenu le nécessite — par exemple à 860px si votre mise en page change de comportement entre md (768px) et lg (992px). La règle d'or reste : placez un breakpoint là où votre contenu en a besoin, en utilisant les valeurs Bootstrap comme ancres de référence.
Évitez de multiplier les breakpoints. Deux ou trois paliers majeurs couvrent la grande majorité des besoins. Chaque breakpoint supplémentaire est du code à maintenir. Si vous vous retrouvez avec six breakpoints différents, c'est souvent le signe que la mise en page de base manque de flexibilité intrinsèque — et qu'il faut retravailler le CSS fondamental plutôt qu'ajouter des media queries.
Connaître les breakpoints ne suffit pas : il faut savoir quoi adapter à chaque palier. Les changements les plus fréquents concernent cinq domaines.
C'est le changement le plus visible. Une mise en page multi-colonnes sur desktop devient une colonne unique sur mobile. Le passage s'effectue généralement au palier md (768px) pour deux colonnes, et lg (992px) pour trois.
/* xs : une colonne */
.grille {
display: flex;
flex-direction: column;
gap: 16px;
}
/* md : deux colonnes */
@media (min-width: 768px) {
.grille { flex-direction: row; flex-wrap: wrap; }
.carte { flex: 1 1 calc(50% - 8px); }
}
/* lg : trois colonnes */
@media (min-width: 992px) {
.carte { flex: 1 1 calc(33.333% - 11px); }
}
Sur mobile (xs, sm), une navigation horizontale à plusieurs liens est rarement viable. La solution CSS la plus simple est de masquer les liens et de les afficher à partir de md ou lg.
/* xs/sm : navigation masquée */
.nav-liens {
display: none;
}
/* md : navigation visible */
@media (min-width: 768px) {
.nav-liens {
display: flex;
gap: 4px;
}
}
Les titres, corps de texte et espacements doivent s'adapter. Un titre de 3.5rem parfait sur desktop est oppressant sur un écran de 375px.
.titre-principal {
font-size: 1.75rem; /* xs : compact */
}
@media (min-width: 768px) {
.titre-principal {
font-size: 2.5rem; /* md : intermédiaire */
}
}
@media (min-width: 1200px) {
.titre-principal {
font-size: 3.5rem; /* xl : généreux */
}
}
Les paddings et marges doivent respirer davantage sur les grands écrans.
.section {
padding: 40px 16px; /* xs : compact */
}
@media (min-width: 768px) {
.section {
padding: 64px 32px; /* md : aéré */
}
}
@media (min-width: 1200px) {
.section {
padding: 100px 40px; /* xl : très généreux */
}
}
Certains éléments n'ont de sens que sur desktop (une sidebar détaillée, un tableau complet) ou uniquement sur mobile (un résumé condensé).
/* Masqué sur mobile, visible sur desktop */
.detail-desktop { display: none; }
@media (min-width: 992px) {
.detail-desktop { display: block; }
}
/* Visible sur mobile, masqué sur desktop */
.resume-mobile { display: block; }
@media (min-width: 992px) {
.resume-mobile { display: none; }
}
Masquer du contenu n'est pas une solution d'adaptation. display: none soustrait un élément visuellement, mais son contenu reste chargé par le navigateur et lu par les lecteurs d'écran. Masquer massivement du contenu sur mobile est le symptôme d'un site conçu uniquement pour desktop. La bonne approche est de concevoir un contenu qui fonctionne sur tous les écrans, présenté différemment selon le contexte.
Objectif : construire la page d'accueil d'une agence créative fictive en mobile-first, en utilisant les breakpoints Bootstrap comme référence. Sur mobile, la page est une succession de blocs lisibles et directs. Sur tablette (md — 768px), la grille de réalisations passe en deux colonnes et la navigation apparaît. Sur desktop (lg — 992px), le hero s'organise en deux zones côte à côte et la grille passe en trois colonnes.
C'est un exercice ancré dans la réalité : hero, grille de projets et footer sont des composants que vous retrouverez dans tout site vitrine ou portfolio.
Consignes HTML :
agence.html avec la balise <meta name="viewport">.<header class="header"> contenant une <div class="header-inner"> avec un <span class="logo"> (texte : FORMA) et une <nav class="nav"> avec quatre <a> : Services, Réalisations, Studio, Contact.<section class="hero"> contenant une <div class="hero-inner"> avec :
<div class="hero-texte"> avec un <h1>, un <p class="accroche"> et un <a class="btn-hero"> (Voir nos travaux).<div class="hero-visuel"> avec trois <div class="forme"> ayant chacune une classe supplémentaire : forme-1, forme-2, forme-3.<section class="realisations"> contenant une <div class="section-inner">, un <h2> et une <div class="grille"> avec six <article class="carte-projet">. Chaque article contient une <div class="carte-visuel"> avec une classe numérotée (cv-1 à cv-6), un <h3> et un <p class="categorie">.<footer class="footer"> avec une <div class="footer-inner"> contenant un <span class="logo">, un <p class="baseline"> et un <p class="copyright">.agence.css.Consignes CSS :
Avant d'écrire quoi que ce soit, imaginez la page à 375px, puis à 768px, puis à 992px. Notez mentalement ce qui doit changer à chaque palier.
CSS de base (palier xs, < 576px) : tout s'empile en colonne. Le header est compact. Le .hero-texte et le .hero-visuel sont l'un sous l'autre. Les .forme sont trois formes géométriques colorées affichées côte à côte à l'intérieur de .hero-visuel. Les six cartes de la grille sont empilées. Identité visuelle sombre : fond #0d0d0d, accent jaune #e8ff47, rose #ff4d6d, turquoise #4dffd4. La navigation est masquée sur mobile.
Palier sm (576px) : aucun changement structurel majeur — c'est le moment d'augmenter légèrement l'espacement et de laisser les formes du hero prendre un peu plus de place.
Palier md (768px) : la navigation apparaît. La grille passe en deux colonnes. Le titre du hero grandit.
Palier lg (992px) : le hero passe en deux zones côte à côte — texte à gauche, formes à droite. La grille passe en trois colonnes. Les espacements deviennent plus généreux.
Palier xl (1200px) : ajustements typographiques finaux. Le <h1> atteint sa taille maximale. Les espacements du hero et des sections s'élargissent encore.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="agence.css">
<title>FORMA — Agence créative</title>
</head>
<body>
<header class="header">
<div class="header-inner">
<span class="logo">FORMA</span>
<nav class="nav">
<a href="#">Services</a>
<a href="#">Réalisations</a>
<a href="#">Studio</a>
<a href="#">Contact</a>
</nav>
</div>
</header>
<section class="hero">
<div class="hero-inner">
<div class="hero-texte">
<h1>Nous créons des expériences qui marquent.</h1>
<p class="accroche">Design, motion et stratégie créative pour les marques qui veulent faire la différence.</p>
<a href="#" class="btn-hero">Voir nos travaux</a>
</div>
<div class="hero-visuel">
<div class="forme forme-1"></div>
<div class="forme forme-2"></div>
<div class="forme forme-3"></div>
</div>
</div>
</section>
<section class="realisations">
<div class="section-inner">
<h2>Nos réalisations</h2>
<div class="grille">
<article class="carte-projet">
<div class="carte-visuel cv-1"></div>
<h3>Identité visuelle Bloom</h3>
<p class="categorie">Branding</p>
</article>
<article class="carte-projet">
<div class="carte-visuel cv-2"></div>
<h3>Campagne digitale Vektra</h3>
<p class="categorie">Motion Design</p>
</article>
<article class="carte-projet">
<div class="carte-visuel cv-3"></div>
<h3>Site e-commerce Noma</h3>
<p class="categorie">Web Design</p>
</article>
<article class="carte-projet">
<div class="carte-visuel cv-4"></div>
<h3>Application mobile Pulse</h3>
<p class="categorie">UI / UX</p>
</article>
<article class="carte-projet">
<div class="carte-visuel cv-5"></div>
<h3>Direction artistique Lune</h3>
<p class="categorie">Branding</p>
</article>
<article class="carte-projet">
<div class="carte-visuel cv-6"></div>
<h3>Packaging Aroki</h3>
<p class="categorie">Print & Packaging</p>
</article>
</div>
</div>
</section>
<footer class="footer">
<div class="footer-inner">
<span class="logo">FORMA</span>
<p class="baseline">Design avec intention.</p>
<p class="copyright">© 2024 FORMA Studio. Tous droits réservés.</p>
</div>
</footer>
</body>
</html>
/* agence.css — Mobile-first avec breakpoints Bootstrap */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* =====================
XS — CSS DE BASE
< 576px
===================== */
body {
font-family: Arial, sans-serif;
font-size: 1rem;
background-color: #0d0d0d;
color: #ffffff;
}
/* --- Header --- */
.header {
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
padding: 0 16px;
}
.header-inner {
max-width: 1320px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
height: 56px;
}
.logo {
font-size: 1.25rem;
font-weight: bold;
letter-spacing: 3px;
color: #e8ff47;
}
/* Navigation masquée sur xs et sm */
.nav {
display: none;
}
/* --- Hero --- */
.hero {
padding: 48px 16px 40px;
}
.hero-inner {
max-width: 1320px;
margin: 0 auto;
}
/* Sur xs : texte et visuel s'empilent dans le flux normal */
.hero-texte {
margin-bottom: 36px;
}
.hero-texte h1 {
font-size: 2rem;
line-height: 1.2;
margin-bottom: 16px;
}
.accroche {
color: rgba(255, 255, 255, 0.6);
font-size: 1rem;
line-height: 1.7;
margin-bottom: 28px;
}
.btn-hero {
display: inline-block;
background-color: #e8ff47;
color: #0d0d0d;
padding: 12px 28px;
text-decoration: none;
font-weight: bold;
font-size: 0.9375rem;
border-radius: 2px;
transition: background-color 0.2s ease;
}
.btn-hero:hover {
background-color: #d4eb30;
}
/* Les trois formes côte à côte dans le hero-visuel */
.hero-visuel {
display: flex;
gap: 12px;
justify-content: center;
}
.forme {
width: 80px;
height: 80px;
flex-shrink: 0;
}
.forme-1 { background-color: #e8ff47; border-radius: 50%; }
.forme-2 { background-color: #ff4d6d; border-radius: 12px; }
.forme-3 { background-color: #4dffd4; border-radius: 0; }
/* --- Réalisations --- */
.realisations {
padding: 48px 16px;
border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.section-inner {
max-width: 1320px;
margin: 0 auto;
}
.realisations h2 {
font-size: 0.8125rem;
text-transform: uppercase;
letter-spacing: 2px;
color: rgba(255, 255, 255, 0.4);
margin-bottom: 24px;
}
/* xs : cartes empilées en une colonne */
.grille {
display: flex;
flex-direction: column;
gap: 16px;
}
.carte-projet {
background-color: #1a1a1a;
border-radius: 8px;
overflow: hidden;
}
.carte-visuel {
height: 180px;
}
.cv-1 { background-color: #1e3a5c; }
.cv-2 { background-color: #3a1a2a; }
.cv-3 { background-color: #1a3a2a; }
.cv-4 { background-color: #3a2a1a; }
.cv-5 { background-color: #2a1a3a; }
.cv-6 { background-color: #3a3a1a; }
.carte-projet h3 {
padding: 14px 16px 4px;
font-size: 0.9375rem;
}
.categorie {
padding: 0 16px 14px;
font-size: 0.75rem;
color: #e8ff47;
text-transform: uppercase;
letter-spacing: 1px;
}
/* --- Footer --- */
.footer {
padding: 40px 16px;
border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.footer-inner {
max-width: 1320px;
margin: 0 auto;
text-align: center;
}
.footer .logo {
display: block;
margin-bottom: 10px;
}
.baseline {
color: rgba(255, 255, 255, 0.4);
font-size: 0.875rem;
margin-bottom: 6px;
}
.copyright {
color: rgba(255, 255, 255, 0.2);
font-size: 0.75rem;
}
/* =====================
SM — 576px
Ajustements d'espace,
pas de changement structurel
===================== */
@media (min-width: 576px) {
.hero {
padding: 60px 24px 48px;
}
.forme {
width: 110px;
height: 110px;
}
.hero-visuel {
gap: 16px;
}
.realisations {
padding: 60px 24px;
}
.footer {
padding: 48px 24px;
}
}
/* =====================
MD — 768px
Navigation visible,
grille en deux colonnes,
titre qui grandit
===================== */
@media (min-width: 768px) {
.header-inner {
height: 64px;
}
/* La navigation apparaît à partir de md */
.nav {
display: flex;
gap: 4px;
}
.nav a {
display: block;
padding: 8px 14px;
color: rgba(255, 255, 255, 0.55);
text-decoration: none;
font-size: 0.875rem;
border-radius: 4px;
transition: color 0.2s ease, background-color 0.2s ease;
}
.nav a:hover {
color: #ffffff;
background-color: rgba(255, 255, 255, 0.06);
}
.hero-texte h1 {
font-size: 2.5rem;
}
/* Grille en deux colonnes */
.grille {
flex-direction: row;
flex-wrap: wrap;
}
.carte-projet {
flex: 1 1 calc(50% - 8px);
}
.realisations {
padding: 64px 32px;
}
}
/* =====================
LG — 992px
Hero en deux colonnes,
grille en trois colonnes,
espacements généreux
===================== */
@media (min-width: 992px) {
.hero {
padding: 80px 32px;
}
/* Hero : texte à gauche, formes à droite */
.hero-inner {
display: flex;
align-items: center;
gap: 60px;
}
.hero-texte {
flex: 1;
margin-bottom: 0;
}
.hero-texte h1 {
font-size: 3rem;
}
.hero-visuel {
flex: 0 0 280px;
flex-direction: column;
justify-content: center;
gap: 16px;
}
.forme {
width: 150px;
height: 150px;
}
/* Grille en trois colonnes */
.carte-projet {
flex: 1 1 calc(33.333% - 11px);
}
.realisations {
padding: 80px 32px;
}
/* Footer en ligne sur desktop */
.footer-inner {
display: flex;
justify-content: space-between;
align-items: center;
text-align: left;
}
.footer .logo {
display: inline;
margin-bottom: 0;
}
}
/* =====================
XL — 1200px
Taille maximale du titre,
espacements finaux
===================== */
@media (min-width: 1200px) {
.hero {
padding: 110px 40px;
}
.hero-texte h1 {
font-size: 3.75rem;
line-height: 1.1;
}
.accroche {
font-size: 1.125rem;
}
.hero-visuel {
flex: 0 0 340px;
}
.forme {
width: 190px;
height: 190px;
}
.carte-visuel {
height: 220px;
}
.realisations {
padding: 100px 40px;
}
}
Cinq paliers Bootstrap utilisés sur six : xs (CSS de base), sm, md, lg et xl. Le palier xxl (1400px) n'est pas utilisé ici car le contenu n'en a pas besoin — la mise en page est déjà optimale à 1200px. C'est l'application directe du principe énoncé en 8.4.1 : on utilise les paliers dont le contenu a besoin, pas tous les paliers systématiquement.
sm sans changement structurel : le palier 576px n'introduit aucun changement de mise en page — uniquement de l'espace et des formes légèrement plus grandes. C'est un breakpoint mineur au sens de 8.4.1. Les breakpoints majeurs sont md (navigation + grille) et lg (hero en colonnes).
Le hero-visuel change de flex-direction à lg : sur mobile et tablette, les trois formes sont côte à côte horizontalement. À partir de lg, .hero-visuel passe en flex-direction: column pour les empiler verticalement dans leur colonne à droite du texte. La même propriété Flexbox produit deux organisations radicalement différentes selon le breakpoint — c'est un bon exemple de ce que "adapter la mise en page" signifie concrètement.
flex: 1 1 calc(50% - 8px) et calc(33.333% - 11px) : avec un gap: 16px, deux cartes côte à côte ont chacune une largeur de calc(50% - 8px) (la moitié du gap). Pour trois colonnes, chaque carte fait calc(33.333% - 11px) (deux tiers du gap répartis sur trois cartes). Cette arithmétique est systématique et peut être réutilisée dans tout projet avec une grille Flexbox.
Erreur fréquente : appliquer tous les paliers Bootstrap mécaniquement, sans réfléchir à ce que le contenu nécessite réellement à chaque seuil. Bootstrap lui-même ne force pas l'utilisation de ses six breakpoints — il les propose comme un vocabulaire commun. L'objectif est d'avoir un CSS lisible, maintenu par des valeurs reconnues, pas un CSS surchargé de media queries inutiles.
Les media queries adaptent la structure de la page, mais un site vraiment responsive va plus loin : ses images ne débordent pas et se chargent au bon poids selon l'écran, ses tailles de police respectent les préférences de l'utilisateur, et ses espacements s'adaptent proportionnellement à la fenêtre. Ces trois aspects — images, unités relatives, unités viewport — sont les derniers outils pour rendre une mise en page truly responsive dans tous ses détails.
Une image est par nature un élément de taille fixe : un fichier de 1200px de large s'affiche à 1200px, point. Sans instruction CSS, elle déborde de n'importe quel conteneur plus étroit. Rendre les images responsives est donc une étape indispensable.
max-width: 100%.C'est la règle responsive la plus simple et la plus universelle. Elle dit à l'image de ne jamais dépasser la largeur de son conteneur, tout en lui permettant d'être plus petite si son fichier l'est.
img {
max-width: 100%;
height: auto; /* Préserve le ratio largeur/hauteur */
display: block; /* Supprime l'espace blanc sous l'image (comportement inline par défaut) */
}
max-width: 100% et non width: 100% : avec width: 100%, une petite image de 200px serait étirée sur toute la largeur de son conteneur, ce qui la pixelliserait. max-width: 100% dit "au maximum la largeur du conteneur" — l'image ne grandit jamais au-delà de sa taille naturelle.
srcset — servir la bonne résolution.Une image affichée à 400px de large sur mobile n'a pas besoin d'être un fichier de 1600px. Charger ce fichier lourd sur mobile est un gaspillage de bande passante. L'attribut HTML srcset permet de fournir plusieurs versions d'une image, et laisse le navigateur choisir la plus adaptée selon la taille d'affichage et la densité de pixels de l'écran.
<img
src="photo-800.jpg"
srcset="
photo-400.jpg 400w,
photo-800.jpg 800w,
photo-1200.jpg 1200w
"
sizes="
(max-width: 576px) 100vw,
(max-width: 992px) 50vw,
33vw
"
alt="Description de l'image"
>
srcset liste les fichiers disponibles avec leur largeur en pixels (w).sizes indique au navigateur la largeur d'affichage prévue selon le contexte. 100vw signifie "toute la largeur de la fenêtre", 50vw signifie "la moitié".src reste présent comme valeur de secours pour les navigateurs qui ne supportent pas srcset.srcset est une suggestion, pas un ordre. Le navigateur reste libre de choisir selon ses propres critères, notamment la densité de l'écran. Sur un écran Retina (densité ×2), il peut choisir l'image 800px pour un affichage prévu à 400px, afin de maintenir la netteté. Cette intelligence est entièrement gérée par le navigateur.
<picture> — changer d'image selon le contexte.srcset sert à choisir entre plusieurs résolutions d'une même image. La balise <picture> va plus loin : elle permet de servir une image complètement différente selon le breakpoint — un cadrage différent, un format différent, voire une image entièrement autre sur mobile et desktop.
<picture>
<!-- Sur les écrans de 992px et plus : image paysage grand format -->
<source media="(min-width: 992px)" srcset="hero-desktop.jpg">
<!-- Sur les écrans de 576px à 991px : version intermédiaire -->
<source media="(min-width: 576px)" srcset="hero-tablette.jpg">
<!-- Fallback : image portrait pour mobile, toujours présent -->
<img src="hero-mobile.jpg" alt="Présentation de l'agence">
</picture>
Les sources sont évaluées dans l'ordre. Le navigateur utilise la première dont la condition media est vérifiée. Si aucune ne l'est, il utilise <img>.
WebP est un format d'image développé par Google qui offre une compression supérieure au JPEG et au PNG, pour une qualité visuelle équivalente — généralement 25 à 35% plus léger. La balise <picture> permet de servir WebP aux navigateurs qui le supportent, avec un fallback JPEG pour les autres.
<picture>
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="Description">
</picture>
Les unités relatives définissent une taille en fonction d'une autre valeur — la taille de police du parent, la taille de police racine du document, ou la taille du conteneur. Elles produisent des mises en page qui s'adaptent naturellement sans media queries supplémentaires.
em — relatif à la taille de police du parent.1em est égal à la taille de police de l'élément parent. Cette unité est utile pour les espacements internes d'un composant, car ils restent proportionnels à sa typographie.
.carte {
font-size: 1rem;
padding: 1.5em; /* 1.5 × 16px = 24px */
border-radius: 0.5em;
}
.carte.grande {
font-size: 1.25rem;
/* padding: 1.5em = 1.5 × 20px = 30px — s'adapte automatiquement */
}
Attention à l'effet de cascade des em. Si vous imbriquez des éléments dont les tailles de police sont en em, les valeurs se multiplient. Un enfant à font-size: 0.8em dans un parent à font-size: 0.8em aura en réalité 0.64em de la taille racine. Pour la typographie, rem est souvent plus prévisible.
rem — relatif à la taille racine du document.1rem est toujours égal à la taille de police de l'élément <html>, quelle que soit la profondeur d'imbrication. Par défaut, les navigateurs définissent cette taille à 16px, mais l'utilisateur peut la modifier dans ses paramètres d'accessibilité. rem est l'unité recommandée pour la typographie et les espacements globaux.
h1 { font-size: 2.5rem; } /* 40px */
h2 { font-size: 1.75rem; } /* 28px */
p { font-size: 1rem; } /* 16px */
small { font-size: 0.875rem; } /* 14px */
.section {
padding: 3rem 1.5rem; /* 48px 24px — reste proportionnel si la racine change */
}
% — relatif à la dimension du parent.Les pourcentages sont relatifs à la dimension correspondante du parent. Ils sont particulièrement utiles pour les mises en page fluides et les grilles.
.colonne-principale { width: 65%; }
.colonne-secondaire { width: 35%; }
/* Sur un conteneur de 800px : 520px et 280px.
Sur 1200px : 780px et 420px.
Les proportions sont préservées quelle que soit la largeur. */
|
|
Taille racine |
Tailles de police, espacements globaux |
|
|
Taille de police du parent |
Padding/margin internes d'un composant |
|
|
Dimension du parent |
Largeurs fluides, grilles |
|
|
Pixel absolu |
Bordures, ombres, valeurs qui ne doivent pas changer |
Unité
Référence
Utiliser pour
Les unités viewport sont relatives aux dimensions de la fenêtre du navigateur, pas du parent. Elles permettent de créer des éléments dont la taille dépend directement de l'espace d'affichage disponible.
vw et vh — largeur et hauteur viewport.1vw = 1% de la largeur de la fenêtre1vh = 1% de la hauteur de la fenêtre100vw = toute la largeur de la fenêtre100vh = toute la hauteur de la fenêtre<!-- unites-viewport.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="unites-viewport.css">
<title>Unités viewport</title>
</head>
<body>
<section class="hero-fullscreen">
<h1>Bienvenue</h1>
<p>Cette section occupe exactement la hauteur de la fenêtre.</p>
</section>
<section class="section-normale">
<h2>Contenu suivant</h2>
<p>Cette section suit normalement dans le flux.</p>
</section>
</body>
</html>
/* unites-viewport.css */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: Arial, sans-serif; }
.hero-fullscreen {
width: 100%;
height: 100vh; /* Occupe exactement la hauteur de la fenêtre */
background-color: #1A3A5C;
color: #ffffff;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 0 5vw; /* Padding proportionnel à la largeur de la fenêtre */
}
.hero-fullscreen h1 {
font-size: 5vw; /* Titre dont la taille suit la largeur de la fenêtre */
margin-bottom: 1rem;
}
.hero-fullscreen p {
font-size: 1.25rem;
color: rgba(255, 255, 255, 0.7);
}
.section-normale {
padding: 60px 24px;
max-width: 800px;
margin: 0 auto;
}
vw pour la typographie fluide.L'unité vw permet de créer des titres dont la taille s'adapte en continu à la largeur de l'écran, sans breakpoints. Combinée à clamp(), elle devient l'outil moderne de référence pour la typographie responsive.
/* clamp(valeur-min, valeur-préférée, valeur-max) */
h1 {
/*
Le titre fait au minimum 1.75rem, au maximum 4rem,
et idéalement 5vw entre les deux.
Sur 320px : 5vw = 16px → en dessous du min → 1.75rem.
Sur 600px : 5vw = 30px → dans la plage → 30px.
Sur 1400px : 5vw = 70px → au-dessus du max → 4rem.
*/
font-size: clamp(1.75rem, 5vw, 4rem);
}
p {
font-size: clamp(1rem, 1.5vw, 1.25rem);
}
clamp() est la technique moderne de typographie fluide. Elle remplace les media queries typographiques à répétition par une seule règle qui s'adapte en continu. Les trois arguments sont : la valeur minimale, la valeur idéale (en vw), et la valeur maximale. Supportée par tous les navigateurs modernes.
dvh — la hauteur viewport dynamique sur mobile.Sur mobile, la barre d'adresse du navigateur apparaît et disparaît en scrollant. 100vh ne tient pas compte de cette barre et peut masquer une partie du contenu. L'unité dvh (Dynamic Viewport Height) résout ce problème en s'adaptant en temps réel à la hauteur réellement disponible.
.hero {
height: 100vh; /* Fallback pour les navigateurs anciens */
height: 100dvh; /* S'adapte à la hauteur réelle disponible sur mobile */
}
vmin et vmax.1vmin = 1% de la plus petite dimension de la fenêtre (largeur ou hauteur)1vmax = 1% de la plus grande dimensionUtiles pour les éléments qui doivent rester proportionnels quelle que soit l'orientation de l'écran.
/* Un avatar qui reste rond et proportionnel en portrait et en paysage */
.avatar {
width: 20vmin;
height: 20vmin;
border-radius: 50%;
}
Objectif : construire la page de lancement d'une application météo fictive. Elle combine un hero plein écran avec typographie fluide, une grille de fonctionnalités responsive et une section de téléchargement. L'exercice travaille 100dvh, clamp(), les images responsives avec max-width: 100% et les unités rem et em pour la typographie et les espacements.
C'est un composant directement réutilisable : landing page, page de présentation de projet, portfolio.
Consignes HTML :
lancement.html avec la balise <meta name="viewport">.<section class="hero"> contenant une <div class="hero-contenu"> avec :
<div class="hero-texte"> contenant un <p class="label"> (Disponible maintenant), un <h1> avec le nom du produit (Aero) et un <span> contenant un sous-titre (L'application météo qui anticipe votre journée.), un <p class="description"> de deux phrases, et une <div class="hero-actions"> avec deux liens : un <a class="btn-principal"> (Télécharger gratuitement) et un <a class="btn-secondaire"> (En savoir plus).<div class="hero-image"> contenant une <img> avec src="mockup.png" et un alt approprié.<section class="fonctionnalites"> avec une <div class="section-inner">, un <h2> et une <div class="grille-fonc"> contenant quatre <div class="fonc">. Chaque .fonc a une <div class="fonc-icone"> (une lettre), un <h3> et un <p>.<section class="telechargement"> avec une <div class="section-inner"> contenant un <h2>, un <p> et une <div class="badges"> avec deux <a class="badge"> (App Store et Google Play).lancement.css.Consignes CSS :
Avant d'écrire la moindre ligne, posez-vous ces questions : comment ce hero se comporte-t-il quand la barre d'adresse du navigateur mobile apparaît ? Comment ce titre reste-t-il lisible à 320px et imposant à 1400px sans une seule media query ?
Hero : la section doit occuper toute la hauteur visible de la fenêtre. Réfléchissez à quelle unité utiliser pour que le contenu ne soit jamais masqué par la barre d'adresse mobile. Prévoyez un fallback pour les anciens navigateurs qui ne supportent pas cette unité. Sur mobile, .hero-texte et .hero-image s'empilent. À partir de md (768px), ils passent côte à côte — le texte doit prendre plus de place que l'image.
Typographie fluide : le <h1> et les titres de section doivent s'adapter en continu entre mobile et desktop, sans media query typographique. Définissez une taille minimale lisible sur 320px, une taille maximale pour les grands écrans, et une valeur intermédiaire proportionnelle à la fenêtre. Quelle fonction CSS permet d'exprimer ces trois contraintes en une seule déclaration ?
Le <span> dans le <h1> : ce sous-titre doit rester proportionnel au titre, quelle que soit la taille calculée de ce dernier. Quelle unité choisir pour garantir cette relation proportionnelle sans valeur absolue ?
Image : l'image mockup.png ne doit jamais déborder de son conteneur ni se déformer, quelle que soit la largeur de l'écran. Quelles propriétés CSS garantissent ces deux comportements ?
Grille de fonctionnalités : une colonne sur mobile, deux à sm (576px), quatre à lg (992px). Utilisez les breakpoints Bootstrap.
Unités : toutes les tailles de police en rem. Les paddings globaux des sections en rem. Les paddings internes des composants (.fonc, .badge) en em pour rester proportionnels à leur propre typographie.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="style.css">
<title>Aero — L'application météo</title>
</head>
<body>
<section class="hero">
<div class="hero-contenu">
<div class="hero-texte">
<p class="label">Disponible maintenant</p>
<h1>Aero <span>L'application météo qui anticipe votre journée.</span></h1>
<p class="description">Aero analyse les données météo en temps réel et vous propose un planning adapté à vos habitudes. Fini les surprises, bonjour les bonnes décisions.</p>
<div class="hero-actions">
<a href="#" class="btn-principal">Télécharger gratuitement</a>
<a href="#" class="btn-secondaire">En savoir plus</a>
</div>
</div>
<div class="hero-image">
<img src="meteo.png" alt="Capture d'écran de l'application Aero sur smartphone">
</div>
</div>
</section>
<section class="fonctionnalites">
<div class="section-inner">
<h2>Conçu pour votre quotidien</h2>
<div class="grille-fonc">
<div class="fonc">
<div class="fonc-icone">P</div>
<h3>Prévisions précises</h3>
<p>Données actualisées toutes les heures depuis 50 000 stations météo mondiales.</p>
</div>
<div class="fonc">
<div class="fonc-icone">A</div>
<h3>Alertes intelligentes</h3>
<p>Notifications personnalisées selon vos activités programmées dans votre agenda.</p>
</div>
<div class="fonc">
<div class="fonc-icone">C</div>
<h3>Carte interactive</h3>
<p>Visualisez les fronts météo, la pluie et le vent en temps réel sur une carte.</p>
</div>
<div class="fonc">
<div class="fonc-icone">M</div>
<h3>Mode hors-ligne</h3>
<p>Les prévisions des 48 prochaines heures sont disponibles sans connexion.</p>
</div>
</div>
</div>
</section>
<section class="telechargement">
<div class="section-inner">
<h2>Gratuit. Sans publicité. Pour toujours.</h2>
<p>Disponible sur iOS et Android. Plus de 2 millions d'utilisateurs actifs.</p>
<div class="badges">
<a href="#" class="badge">App Store</a>
<a href="#" class="badge">Google Play</a>
</div>
</div>
</section>
</body>
</html>
/* lancement.css */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
font-size: 1rem;
background-color: #f8fafd;
color: #1a1a2e;
}
/* ======================
HERO — XS (mobile)
====================== */
.hero {
background: linear-gradient(135deg, #0f2a4a 0%, #1a3a5c 60%, #2e6da4 100%);
padding: 60px 20px;
/*
100vh d'abord comme fallback pour les anciens navigateurs.
100dvh ensuite : hauteur dynamique qui s'ajuste en temps réel
à la barre d'adresse mobile — le contenu n'est jamais masqué.
*/
min-height: 100vh;
min-height: 100dvh;
display: flex;
align-items: center;
}
.hero-contenu {
max-width: 1140px;
margin: 0 auto;
width: 100%;
/* Sur mobile : .hero-texte et .hero-image s'empilent dans le flux */
}
.hero-texte {
margin-bottom: 36px;
}
.label {
display: inline-block;
background-color: rgba(232, 255, 71, 0.15);
color: #e8ff47;
font-size: 0.8125rem;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1.5px;
padding: 5px 14px;
border-radius: 20px;
margin-bottom: 1.25rem;
border: 1px solid rgba(232, 255, 71, 0.3);
}
h1 {
/*
clamp(min, préféré, max) : typographie fluide sans media query.
Minimum 1.75rem → lisible à 320px.
5vw → proportionnel à la fenêtre.
3.75rem → imposant mais pas excessif sur 1400px.
*/
font-size: clamp(1.75rem, 5vw, 3.75rem);
line-height: 1.15;
color: #ffffff;
margin-bottom: 1rem;
}
h1 span {
display: block;
/*
0.55em : relatif au h1 parent.
Si h1 = 60px, span = 33px.
Si h1 = 28px, span = 15.4px.
La proportion est maintenue automatiquement par clamp().
*/
font-size: 0.55em;
color: rgba(255, 255, 255, 0.65);
font-weight: normal;
margin-top: 0.5em;
line-height: 1.4;
}
.description {
font-size: clamp(1rem, 1.5vw, 1.25rem);
color: rgba(255, 255, 255, 0.7);
line-height: 1.75;
margin-bottom: 2rem;
max-width: 540px;
}
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
}
.btn-principal {
display: inline-block;
background-color: #e8ff47;
color: #0f2a4a;
padding: 0.875rem 1.75rem;
text-decoration: none;
font-weight: bold;
font-size: 0.9375rem;
border-radius: 4px;
white-space: nowrap;
transition: background-color 0.2s ease;
}
.btn-principal:hover { background-color: #d4eb30; }
.btn-secondaire {
display: inline-block;
color: rgba(255, 255, 255, 0.8);
padding: 0.875rem 1.75rem;
text-decoration: none;
font-size: 0.9375rem;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 4px;
white-space: nowrap;
transition: border-color 0.2s ease, color 0.2s ease;
}
.btn-secondaire:hover {
border-color: rgba(255, 255, 255, 0.7);
color: #ffffff;
}
.hero-image { text-align: center; }
/*
max-width: 100% : l'image ne dépasse jamais son conteneur.
height: auto : les proportions sont toujours préservées.
display: block : supprime l'espace blanc sous l'image.
*/
.hero-image img {
max-width: 100%;
height: auto;
display: block;
margin: 0 auto;
max-height: 380px;
object-fit: contain;
border-radius: 16px;
background-color: rgba(255, 255, 255, 0.05);
min-width: 180px;
min-height: 180px;
}
/* ======================
FONCTIONNALITÉS — XS
====================== */
.fonctionnalites {
padding: 4rem 1.25rem;
}
.section-inner {
max-width: 1140px;
margin: 0 auto;
}
.fonctionnalites h2 {
font-size: clamp(1.375rem, 3vw, 2rem);
color: #1a1a2e;
text-align: center;
margin-bottom: 2.5rem;
}
/* xs : une colonne */
.grille-fonc {
display: flex;
flex-direction: column;
gap: 1rem;
}
.fonc {
background-color: #ffffff;
border: 1px solid #e0e8f5;
border-radius: 10px;
padding: 1.5em; /* em : proportionnel à la police du composant */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.fonc-icone {
width: 2.75em;
height: 2.75em;
border-radius: 8px;
background-color: #1a3a5c;
color: #e8ff47;
font-size: 1.125rem;
font-weight: bold;
line-height: 2.75em;
text-align: center;
margin-bottom: 1em;
}
.fonc h3 {
font-size: 1rem;
color: #1a1a2e;
margin-bottom: 0.5em;
}
.fonc p {
font-size: 0.875rem;
color: #666;
line-height: 1.65;
}
/* ======================
TÉLÉCHARGEMENT — XS
====================== */
.telechargement {
background-color: #1a3a5c;
padding: 4rem 1.25rem;
text-align: center;
}
.telechargement h2 {
font-size: clamp(1.25rem, 3vw, 1.875rem);
color: #ffffff;
margin-bottom: 0.75rem;
}
.telechargement p {
color: rgba(255, 255, 255, 0.6);
font-size: 1rem;
margin-bottom: 2rem;
}
.badges {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 1rem;
}
.badge {
display: inline-block;
background-color: rgba(255, 255, 255, 0.08);
color: #ffffff;
padding: 0.875em 2em; /* em : proportionnel à la taille du badge */
text-decoration: none;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
font-size: 0.9375rem;
font-weight: bold;
transition: background-color 0.2s ease, border-color 0.2s ease;
}
.badge:hover {
background-color: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.5);
}
/* ======================
SM — 576px
Grille en deux colonnes
====================== */
@media (min-width: 576px) {
.grille-fonc {
flex-direction: row;
flex-wrap: wrap;
}
.fonc {
flex: 1 1 calc(50% - 0.5rem);
}
}
/* ======================
MD — 768px
Hero en deux colonnes
====================== */
@media (min-width: 768px) {
.hero { padding: 80px 32px; }
.hero-contenu {
display: flex;
align-items: center;
gap: 60px;
}
/* Le texte prend 3/5 de l'espace, l'image 2/5 */
.hero-texte {
flex: 3;
margin-bottom: 0;
}
.hero-image {
flex: 2;
}
.hero-image img { max-height: 480px; }
.fonctionnalites { padding: 5rem 2rem; }
.telechargement { padding: 5rem 2rem; }
}
/* ======================
LG — 992px
Grille en quatre colonnes
====================== */
@media (min-width: 992px) {
.fonc {
flex: 1 1 calc(25% - 0.75rem);
}
.fonctionnalites { padding: 6rem 2rem; }
.telechargement { padding: 6rem 2rem; }
}
/* ======================
XL — 1200px
Espacements finaux
====================== */
@media (min-width: 1200px) {
.hero { padding: 100px 40px; }
.hero-image img { max-height: 560px; }
}
min-height: 100vh puis min-height: 100dvh : les deux lignes coexistent. Les navigateurs modernes lisent les deux et appliquent la seconde (dvh). Les anciens ignorent dvh et conservent vh. C'est le pattern de fallback progressif standard — toujours écrire l'ancienne valeur en premier, la nouvelle par-dessus.
clamp() sur tous les titres : chaque <h2> et le <h1> utilisent clamp(). Aucune media query typographique n'est nécessaire. La valeur centrale en vw assure la transition fluide, les bornes min et max garantissent la lisibilité et l'équilibre visuel à toutes les tailles.
font-size: 0.55em sur le <span> dans <h1> : en em, le sous-titre est proportionnel au titre calculé par clamp(). Si clamp() retourne 60px sur grand écran, le span fait 33px. Sur mobile à 28px, il fait 15.4px. La proportion est maintenue automatiquement, sans aucune media query supplémentaire.
padding: 1.5em sur .fonc : le padding interne de chaque carte est en em, proportionnel à sa propre font-size. Si on décide un jour d'augmenter la police de .fonc, les espacements internes s'agrandissent avec elle de manière cohérente.
max-width: 100% + height: auto sur l'image : l'image ne déborde jamais de son conteneur et conserve ses proportions quelle que soit la largeur. object-fit: contain s'assure que même contrainte par max-height, elle n'est pas déformée. Le fond translucide visible si mockup.png n'existe pas montre l'espace réservé pendant le chargement.
Les transformations CSS permettent de modifier l'apparence visuelle d'un élément — sa position, son orientation, sa taille, son inclinaison — sans affecter le flux du document. Un élément transformé continue d'occuper son espace d'origine dans la mise en page : les éléments voisins ne bougent pas. C'est une différence fondamentale avec margin ou position, qui eux modifient le flux. Les transformations agissent uniquement sur le rendu visuel, ce qui les rend particulièrement performantes et adaptées aux animations.
Toutes les transformations s'appliquent via la propriété transform, qui accepte une ou plusieurs fonctions de transformation. Quand plusieurs fonctions sont combinées, elles s'appliquent de droite à gauche.
translate() — déplacement.Déplace un élément horizontalement et/ou verticalement par rapport à sa position d'origine, sans sortir du flux.
<!-- translate.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="translate.css">
<title>translate()</title>
</head>
<body>
<div class="conteneur">
<div class="boite reference">Position d'origine</div>
<div class="boite deplacee">Déplacée</div>
<div class="boite suivante">Élément suivant — non affecté</div>
</div>
</body>
</html>
/* translate.css */
body { font-family: Arial, sans-serif; padding: 40px; background-color: #f4f4f4; }
.conteneur {
display: flex;
flex-direction: column;
gap: 12px;
max-width: 400px;
}
.boite {
background-color: #2E6DA4;
color: #ffffff;
padding: 16px 20px;
border-radius: 4px;
font-size: 0.875rem;
transition: transform 0.8s ease;
}
.reference {
background-color: #dde3ed;
color: #555;
}
.deplacee {
transform: translate(0px, 0px);
}
.deplacee:hover {
transform: translate(30px, -10px);
}
.suivante {
background-color: #1A3A5C;
}
rotate() — rotation.Fait pivoter un élément autour de son point d'origine (par défaut le centre). L'angle s'exprime en degrés (deg), mais aussi en tours (turn) ou en radians (rad).
<!-- rotate.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="rotate.css">
<title>rotate()</title>
</head>
<body>
<div class="galerie">
<div class="carte">Rotation 0°</div>
<div class="carte rot-15">Rotation 15°</div>
<div class="carte rot-neg">Rotation −10°</div>
<div class="carte rot-45">Rotation 45°</div>
<div class="carte rot-demi">0.5 tour</div>
</div>
</body>
</html>
/* rotate.css */
body { font-family: Arial, sans-serif; padding: 60px; background-color: #f4f4f4; }
.galerie {
display: flex;
gap: 40px;
align-items: center;
flex-wrap: wrap;
}
.carte {
background-color: #2E6DA4;
color: #ffffff;
padding: 20px 24px;
border-radius: 4px;
font-size: 0.875rem;
font-weight: bold;
text-align: center;
width: 120px;
transform: rotate(0deg);
transition: transform 0.8s ease;
}
.rot-demi { background-color: #1A3A5C; }
.rot-15:hover { transform: rotate(15deg); }
.rot-neg:hover { transform: rotate(-10deg); }
.rot-45:hover { transform: rotate(45deg); }
.rot-demi:hover { transform: rotate(0.5turn); }
scale() — mise à l'échelle.Agrandit ou rétrécit un élément. scale(1) est la taille naturelle. scale(2) double la taille. scale(0.5) réduit de moitié. L'élément transformé continue d'occuper son espace d'origine dans le flux — l'espace autour de lui ne change pas.
<!-- scale.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="scale.css">
<title>scale()</title>
</head>
<body>
<div class="galerie">
<div class="boite">scale(1)<br>Normal</div>
<div class="boite agrandie">scale(1.3)<br>Agrandi</div>
<div class="boite reduite">scale(0.7)<br>Réduit</div>
<div class="boite miroir">scale(−1, 1)<br>Miroir horizontal</div>
</div>
</body>
</html>
/* scale.css */
body { font-family: Arial, sans-serif; padding: 80px; background-color: #f4f4f4; }
.galerie {
display: flex;
gap: 40px;
align-items: center;
flex-wrap: wrap;
}
.boite {
background-color: #2E6DA4;
color: #ffffff;
padding: 20px;
border-radius: 4px;
font-size: 0.8125rem;
text-align: center;
width: 110px;
line-height: 1.5;
transition: transform 0.8s ease;
}
.agrandie { transform: scale(1); background-color: #1A3A5C; }
.reduite { transform: scale(1); background-color: #4a8ac4; }
.miroir { transform: scale(1, 1); background-color: #C0392B; }
.agrandie:hover { transform: scale(1.3); }
.reduite:hover { transform: scale(0.7); }
.miroir:hover { transform: scale(-1, 1); }
skew() — inclinaison.Incline un élément le long des axes horizontal et/ou vertical, créant un effet de cisaillement.
.element { transform: skewX(20deg); } /* Inclinaison horizontale */
.element { transform: skewY(10deg); } /* Inclinaison verticale */
.element { transform: skew(20deg, 10deg);} /* Les deux axes */
transform-origin — changer le point d'ancrage.Par défaut, toutes les transformations pivotent autour du centre de l'élément. transform-origin permet de déplacer ce point d'ancrage.
<!-- transform-origin.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="transform-origin.css">
<title>transform-origin</title>
</head>
<body>
<div class="scene">
<div class="boite origine-centre">Centre (défaut)</div>
<div class="boite origine-haut-gauche">Haut gauche</div>
<div class="boite origine-bas-droite">Bas droite</div>
</div>
</body>
</html>
/* transform-origin.css */
body { font-family: Arial, sans-serif; padding: 60px; background-color: #f4f4f4; }
.scene {
display: flex;
gap: 60px;
align-items: center;
}
.boite {
background-color: #2E6DA4;
color: #ffffff;
padding: 20px 16px;
border-radius: 4px;
font-size: 0.8125rem;
text-align: center;
width: 120px;
transform: rotate(0deg);
transition: transform 0.8s ease;
}
.origine-centre { transform-origin: center center; }
.origine-haut-gauche { transform-origin: top left; background-color: #C0392B; }
.origine-bas-droite { transform-origin: bottom right; background-color: #1A3A5C; }
.boite:hover { transform: rotate(20deg); }
On peut chaîner plusieurs fonctions dans une même déclaration transform. Attention : l'ordre compte. Les transformations s'appliquent de droite à gauche — la dernière écrite est la première appliquée.
/* Déplace d'abord, puis fait pivoter */
.element {
transform: rotate(45deg) translate(100px, 0);
}
/* Fait pivoter d'abord, puis déplace
(résultat visuel différent du précédent) */
.element {
transform: translate(100px, 0) rotate(45deg);
}
/* Combinaison typique pour un effet de bouton au survol */
.btn:hover {
transform: translateY(-3px) scale(1.02);
}
transform ne crée pas de scroll horizontal. Contrairement à position: relative avec left: -200px, une transformation négative ne génère pas de barre de défilement. Le moteur de rendu gère les transformations après le calcul du flux — elles sont purement visuelles.
Les transformations 3D ajoutent un troisième axe — la profondeur, noté Z — qui permet de faire basculer, pivoter ou déplacer des éléments dans un espace tridimensionnel simulé. Elles reposent sur deux mécanismes supplémentaires : la perspective et la profondeur de scène.
Sans perspective, les transformations 3D existent mais ne produisent aucun effet de profondeur visible. La propriété perspective définit la distance entre l'observateur et le plan de l'écran. Plus cette valeur est petite, plus l'effet de perspective est prononcé.
/*
perspective s'applique sur le CONTENEUR parent, pas sur l'élément transformé.
C'est une règle fondamentale souvent source d'erreur.
*/
.scene {
perspective: 600px; /* Distance de l'observateur — plus petit = plus fort */
}
.carte {
transform: rotateY(45deg); /* Rotation 3D autour de l'axe vertical */
}
<!-- rotations-3d.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="rotations-3d.css">
<title>Rotations 3D</title>
</head>
<body>
<h2>Avec perspective — effet de profondeur visible</h2>
<div class="scene avec-perspective">
<div class="boite">rotateX(40deg)</div>
<div class="boite rot-y">rotateY(40deg)</div>
<div class="boite rot-z">rotateZ(40deg)</div>
</div>
<h2>Sans perspective — les rotations semblent plates</h2>
<div class="scene sans-perspective">
<div class="boite">rotateX(40deg)</div>
<div class="boite rot-y">rotateY(40deg)</div>
<div class="boite rot-z">rotateZ(40deg)</div>
</div>
</body>
</html>
/* rotations-3d.css */
body { font-family: Arial, sans-serif; padding: 40px; background-color: #f4f4f4; }
h2 {
color: #1A3A5C;
font-size: 0.9375rem;
margin-bottom: 24px;
margin-top: 40px;
}
.scene {
display: flex;
gap: 40px;
align-items: center;
flex-wrap: wrap;
margin-bottom: 20px;
}
.avec-perspective { perspective: 500px; }
.sans-perspective { }
.boite {
color: #ffffff;
padding: 24px 20px;
border-radius: 4px;
font-size: 0.8125rem;
text-align: center;
width: 130px;
line-height: 1.5;
transition: transform 0.8s ease;
}
.boite { transform: rotateX(0deg); background-color: #C0392B; }
.rot-y { transform: rotateY(0deg); background-color: #2E6DA4; }
.rot-z { transform: rotateZ(0deg); background-color: #1A3A5C; }
.boite:hover { transform: rotateX(40deg); }
.rot-y:hover { transform: rotateY(40deg); }
.rot-z:hover { transform: rotateZ(40deg); }
translateZ() et perspective-origin.translateZ() rapproche ou éloigne un élément de l'observateur. Une valeur positive rapproche (l'élément paraît plus grand), une valeur négative éloigne (l'élément paraît plus petit).
perspective-origin déplace le point de fuite de la perspective — l'équivalent du point de vue de l'observateur.
.scene {
perspective: 600px;
perspective-origin: center center;
width: 300px;
height: 200px;
display: flex;
align-items: center;
justify-content: space-around;
}
.proche {
transform: translateZ(0px);
transition: transform 0.8s ease;
width: 80px;
height: 80px;
background: #e74c3c;
color: white;
display: flex;
align-items: center;
justify-content: center;
}
.proche:hover {
transform: translateZ(100px);
}
.lointain {
transform: translateZ(0px);
transition: transform 0.8s ease;
width: 80px;
height: 80px;
background: #3498db;
color: white;
display: flex;
align-items: center;
justify-content: center;
}
.lointain:hover {
transform: translateZ(-100px);
}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Perspective 3D</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="scene">
<div class="proche">Proche</div>
<div class="lointain">Lointain</div>
</div>
</body>
</html>
transform-style: preserve-3d.Par défaut, les enfants d'un élément transformé en 3D sont aplatis dans le plan de l'écran. transform-style: preserve-3d préserve l'espace 3D pour les éléments enfants, ce qui permet de construire des objets 3D composites comme des cubes ou des cartes retournables.
.scene {
perspective: 800px;
display: flex;
align-items: center;
justify-content: center;
height: 200px;
}
.objet-3d {
transform-style: preserve-3d;
transform: rotateY(0deg);
transition: transform 1s ease;
position: relative;
width: 150px;
height: 150px;
}
.scene:hover .objet-3d {
transform: rotateY(45deg);
}
.face-avant,
.face-arriere {
position: absolute;
width: 150px;
height: 150px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.2rem;
}
.face-avant {
transform: translateZ(50px);
background: #e74c3c;
}
.face-arriere {
transform: translateZ(-50px);
background: #3498db;
}
Les transformations 3D et les performances : les transformations utilisant translateZ() ou translate3d() — même avec une valeur nulle — activent l'accélération matérielle du GPU dans la plupart des navigateurs. C'est pourquoi on voit parfois transform: translateZ(0) utilisé comme hack de performance pour forcer cette accélération sur des animations complexes. Cette technique est moins nécessaire sur les navigateurs modernes, mais reste ancrée dans de nombreuses bases de code.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Transform 3D</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="scene">
<div class="objet-3d">
<div class="face-avant">Avant</div>
<div class="face-arriere">Arrière</div>
</div>
</div>
</body>
</html>
Objectif : construire une grille de cartes de compétences techniques avec des effets de transformation au survol. Chaque carte se soulève et s'agrandit légèrement au passage de la souris. L'une d'elles intègre un effet de retournement 3D révélant une face cachée avec une description. L'exercice travaille translate, scale, rotate, transform-origin et l'introduction au 3D avec rotateY et preserve-3d.
C'est un composant directement réutilisable : grille de compétences pour un portfolio, cartes de services pour un site vitrine, présentation de fonctionnalités.
Consignes HTML :
competences.html avec la balise <meta name="viewport">.<section class="section-comp"> avec un <h1> et une <div class="grille">..grille, créez six <div class="carte">. Les cinq premières ont deux enfants : une <div class="carte-icone"> (une lettre ou emoji technique) et un <div class="carte-corps"> avec un <h2> et un <p> court.carte-flip. Elle contient deux faces : une <div class="face face-avant"> (icône + titre) et une <div class="face face-arriere"> (un <h2> et une description plus longue).competences.css.Consignes CSS :
Avant d'écrire quoi que ce soit, imaginez ce que l'utilisateur ressent au survol de chaque carte. La transformation doit renforcer l'interaction, pas la parasiter.
Mise en page : la grille utilise Flexbox avec flex-wrap. Les cartes font environ 280px de large minimum et s'adaptent à l'espace disponible. Fond de page sombre.
Cartes simples (les cinq premières) : au repos, chaque carte a un fond sombre, du padding, une bordure subtile et une ombre légère. Au survol, la carte doit se soulever visuellement et s'agrandir très légèrement. Réfléchissez à quelles fonctions de transformation combiner pour obtenir cet effet. La transition doit être fluide — nous n'avons pas encore étudié les transitions formellement, mais la propriété transition: transform 0.25s ease suffit pour cet exercice.
.carte-icone : un grand symbole centré dans un cercle ou carré arrondi coloré. Au survol de la carte, l'icône effectue une légère rotation. Réfléchissez à quel transform-origin utiliser pour que la rotation semble naturelle.
Carte retournable (.carte-flip) : la carte se retourne sur l'axe vertical au survol pour révéler sa face arrière. Pour cela : le conteneur .carte-flip a une hauteur fixe et sert de scène avec perspective. Les deux .face sont superposées avec position: absolute et pleine largeur/hauteur. La face arrière est pré-retournée de 180° pour être lisible une fois le retournement effectué. Au survol de .carte-flip, l'élément intérieur effectue une rotation de 180° autour de l'axe Y. backface-visibility: hidden cache chaque face quand elle est dos à l'observateur.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="competences.css">
<title>Compétences techniques</title>
</head>
<body>
<section class="section-comp">
<h1>Compétences</h1>
<div class="grille">
<div class="carte">
<div class="carte-icone ic-html">H</div>
<div class="carte-corps">
<h2>HTML</h2>
<p>Structure sémantique, accessibilité, formulaires et balises modernes HTML5.</p>
</div>
</div>
<div class="carte">
<div class="carte-icone ic-css">C</div>
<div class="carte-corps">
<h2>CSS</h2>
<p>Flexbox, Grid, animations, responsive design et architecture CSS scalable.</p>
</div>
</div>
<div class="carte">
<div class="carte-icone ic-js">J</div>
<div class="carte-corps">
<h2>JavaScript</h2>
<p>ES6+, manipulation du DOM, fetch API, programmation asynchrone.</p>
</div>
</div>
<div class="carte">
<div class="carte-icone ic-react">R</div>
<div class="carte-corps">
<h2>React</h2>
<p>Composants, hooks, gestion d'état et intégration d'API REST.</p>
</div>
</div>
<div class="carte">
<div class="carte-icone ic-git">G</div>
<div class="carte-corps">
<h2>Git</h2>
<p>Versioning, branches, pull requests et collaboration en équipe.</p>
</div>
</div>
<!-- Carte retournable -->
<div class="carte carte-flip">
<div class="carte-flip-inner">
<div class="face face-avant">
<div class="carte-icone ic-figma">F</div>
<div class="carte-corps">
<h2>Figma</h2>
<p>Survolez pour en savoir plus</p>
</div>
</div>
<div class="face face-arriere">
<h2>Figma</h2>
<p>Conception d'interfaces, prototypage interactif, création de design systems et collaboration en temps réel avec les équipes produit et développement.</p>
</div>
</div>
</div>
</div>
</section>
</body>
</html>
/* competences.css */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
background-color: #0d1117;
color: #e6edf3;
padding: 60px 24px;
}
.section-comp {
max-width: 1000px;
margin: 0 auto;
}
.section-comp h1 {
font-size: 2rem;
color: #e6edf3;
margin-bottom: 40px;
letter-spacing: -0.5px;
}
/* --- GRILLE --- */
.grille {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
/* --- CARTES SIMPLES --- */
.carte {
flex: 1 1 280px;
background-color: #161b22;
border: 1px solid #30363d;
border-radius: 10px;
padding: 24px;
display: flex;
flex-direction: column;
gap: 16px;
cursor: default;
/*
transition sur transform et box-shadow pour anticiper le survol.
On utilise transition ici de façon pragmatique avant la section 9.2.
*/
transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.carte:hover {
/*
translateY(-6px) : soulève la carte de 6px vers le haut.
scale(1.02) : agrandit très légèrement pour accentuer l'effet.
Combinés, ils donnent une impression de profondeur et d'interactivité.
*/
transform: translateY(-6px) scale(1.02);
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5);
}
/* --- ICÔNES --- */
.carte-icone {
width: 52px;
height: 52px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.375rem;
font-weight: bold;
color: #ffffff;
/*
transform-origin: bottom center :
la rotation part du bas de l'icône, comme une aiguille qui pivote.
L'effet est plus naturel qu'une rotation autour du centre.
*/
transform-origin: bottom center;
transition: transform 0.3s ease;
}
/* Au survol de la carte, l'icône tourne légèrement */
.carte:hover .carte-icone {
transform: rotate(-8deg);
}
/* Couleurs d'icône par technologie */
.ic-html { background-color: #e34c26; }
.ic-css { background-color: #264de4; }
.ic-js { background-color: #f0db4f; color: #0d1117; }
.ic-react { background-color: #61dafb; color: #0d1117; }
.ic-git { background-color: #f05032; }
.ic-figma { background-color: #a259ff; }
.carte-corps h2 {
font-size: 1rem;
color: #e6edf3;
margin-bottom: 6px;
}
.carte-corps p {
font-size: 0.8125rem;
color: #8b949e;
line-height: 1.6;
}
/* --- CARTE RETOURNABLE --- */
/*
La carte-flip est la scène 3D : elle définit la perspective
et les dimensions. Elle ne se transforme pas elle-même.
*/
.carte-flip {
perspective: 900px;
/*
On retire les propriétés de la carte standard
qui entreraient en conflit avec la structure 3D.
*/
padding: 0;
background: none;
border: none;
cursor: pointer;
min-height: 200px;
}
/* Annuler le hover de .carte sur .carte-flip */
.carte-flip:hover {
transform: none;
box-shadow: none;
}
/*
L'élément intérieur est celui qui se retourne.
transform-style: preserve-3d : ses enfants (face-avant et face-arriere)
vivent dans l'espace 3D — sans ça, la face arrière serait invisible.
*/
.carte-flip-inner {
position: relative;
width: 100%;
height: 100%;
min-height: 200px;
transform-style: preserve-3d;
transition: transform 0.6s ease;
}
/* Au survol, l'inner fait un demi-tour sur l'axe Y */
.carte-flip:hover .carte-flip-inner {
transform: rotateY(180deg);
}
/* Les deux faces sont superposées en position absolue */
.face {
position: absolute;
inset: 0; /* Équivalent à top:0; right:0; bottom:0; left:0 */
border-radius: 10px;
padding: 24px;
display: flex;
flex-direction: column;
gap: 16px;
/*
backface-visibility: hidden :
cache la face quand elle est dos à l'observateur (retournée de 180°).
Sans ça, on verrait les deux faces en même temps, en miroir.
*/
backface-visibility: hidden;
}
.face-avant {
background-color: #161b22;
border: 1px solid #30363d;
}
.face-arriere {
background-color: #1a3a5c;
border: 1px solid #2e6da4;
/*
La face arrière est pré-retournée de 180°.
Quand l'inner fait son demi-tour, elle revient à 0° et devient lisible.
*/
transform: rotateY(180deg);
justify-content: center;
}
.face-arriere h2 {
font-size: 1rem;
color: #a8c4e0;
margin-bottom: 4px;
}
.face-arriere p {
font-size: 0.8125rem;
color: #c8daf0;
line-height: 1.65;
}
L'ordre des transformations dans .carte:hover : translateY(-6px) scale(1.02) applique d'abord le scale (de droite à gauche), puis le translate. L'ordre inverse — scale(1.02) translateY(-6px) — produirait un déplacement légèrement différent car le scale modifie le référentiel dans lequel translate opère. Dans ce cas précis la différence est imperceptible, mais garder cette règle en tête évite des surprises sur des transformations plus complexes.
transform-origin: bottom center sur .carte-icone : avec le centre par défaut, la rotation de l'icône ressemblerait à une roue qui tourne sur place. Avec le bas comme point d'ancrage, l'icône se balance comme un pendule — ce qui est visuellement plus organique et agréable.
perspective sur .carte-flip, pas sur .carte-flip-inner : c'est l'erreur la plus classique avec les transformations 3D. La perspective définit le point de vue de l'observateur — elle doit être sur le conteneur parent de l'élément qui se transforme. Mise sur l'inner lui-même, elle s'annulerait avec son propre transform et produirait un résultat aplati.
backface-visibility: hidden sur les deux faces : sans cette propriété, pendant le retournement on verrait simultanément les deux faces superposées en miroir. Appliquée sur chaque .face, elle garantit que seule la face orientée vers l'observateur est visible à chaque instant.
inset: 0 : c'est la propriété raccourcie moderne pour top: 0; right: 0; bottom: 0; left: 0. Elle positionne l'élément pour qu'il remplisse exactement son conteneur position: relative. Supportée par tous les navigateurs modernes.
Une transition est le passage animé d'un état CSS à un autre. Sans transition, les changements de propriétés sont instantanés — un bouton change de couleur au survol sans aucune progressivité. Avec une transition, ce changement se produit sur une durée définie, selon une courbe de vitesse choisie. C'est l'outil le plus simple et le plus utilisé pour donner de la fluidité à une interface.
Une transition se définit sur l'état de départ de l'élément, pas sur l'état cible. C'est une distinction fondamentale : en écrivant transition sur .btn, vous dites "quand une propriété de cet élément change, anime ce changement". Peu importe d'où vient le changement — survol, focus, classe ajoutée en JavaScript, attribut modifié.
/* La transition est déclarée sur l'état de départ */
.btn {
background-color: #2E6DA4;
transition: background-color 0.3s ease;
}
/* L'état cible ne contient que les nouvelles valeurs */
.btn:hover {
background-color: #1A3A5C;
}
Syntaxe complète.
.element {
transition: propriété durée timing-function délai;
}
/* Exemples */
.element { transition: opacity 0.3s ease; }
.element { transition: transform 0.5s ease-in-out 0.1s; }
/* Animer plusieurs propriétés : séparées par des virgules */
.element {
transition:
background-color 0.3s ease,
transform 0.2s ease,
box-shadow 0.3s ease;
}
/* Animer toutes les propriétés modifiées */
.element {
transition: all 0.3s ease;
/*
Pratique mais déconseillé en production : "all" anime aussi
des propriétés auxquelles on ne pense pas (height, width, font-size),
ce qui peut produire des effets indésirables et des
performances dégradées. Préférez toujours cibler les propriétés
explicitement.
*/
}
<!-- transitions-base.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="transitions-base.css">
<title>Transitions — base</title>
</head>
<body>
<h2>Sans transition (changement instantané)</h2>
<button class="btn sans-transition">Survolez-moi</button>
<h2>Avec transition</h2>
<button class="btn avec-transition">Survolez-moi</button>
<h2>Plusieurs propriétés</h2>
<button class="btn multi-transition">Survolez-moi</button>
</body>
</html>
/* transitions-base.css */
body { font-family: Arial, sans-serif; padding: 40px; background-color: #f4f4f4; }
h2 { color: #1A3A5C; font-size: 0.9375rem; margin: 24px 0 10px; }
.btn {
display: inline-block;
padding: 12px 28px;
border: none;
border-radius: 6px;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
background-color: #2E6DA4;
color: #ffffff;
}
/* Aucune transition : le changement est instantané */
.sans-transition:hover {
background-color: #C0392B;
transform: scale(1.05);
}
/* Transition sur les propriétés ciblées */
.avec-transition {
transition: background-color 0.35s ease;
}
.avec-transition:hover {
background-color: #C0392B;
}
/* Plusieurs propriétés animées simultanément */
.multi-transition {
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
transition:
background-color 0.3s ease,
transform 0.2s ease,
box-shadow 0.3s ease;
}
.multi-transition:hover {
background-color: #1A3A5C;
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.25);
}
Toutes les propriétés CSS ne peuvent pas être animées. Pour qu'une transition fonctionne, la propriété doit être interpolable — c'est-à-dire que le navigateur doit pouvoir calculer des valeurs intermédiaires entre l'état de départ et l'état d'arrivée.
/* Couleurs */
color, background-color, border-color, outline-color, box-shadow
/* Dimensions et espacement */
width, height, max-width, max-height
padding, margin
border-width, border-radius
/* Positionnement */
top, right, bottom, left
transform (translate, rotate, scale, skew)
/* Visibilité */
opacity
/* Typographie */
font-size, line-height, letter-spacing, word-spacing
/*
Ces propriétés changent instantanément, sans transition possible.
Le navigateur ne peut pas interpoler entre leurs valeurs.
*/
display /* block → flex → none : pas d'état intermédiaire */
visibility /* visible → hidden : discret, pas de dégradé */
position /* static → absolute : changement structural */
font-family /* Impossible de "passer" d'une police à une autre */
display ne peut pas être animé — le cas le plus courant de surprise. Si vous voulez faire apparaître un élément avec un fondu, vous ne pouvez pas animer display: none → display: block. La technique classique est de combiner opacity (animable) avec visibility (non animable mais qui peut être différée) ou de manipuler opacity seule sur un élément toujours présent dans le DOM.
/* Mauvaise approche : display ne s'anime pas */
.menu {
display: none;
transition: display 0.3s ease; /* Ignoré */
}
.menu.ouvert { display: block; }
/* Bonne approche : opacity + visibility */
.menu {
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
.menu.ouvert {
opacity: 1;
visibility: visible;
}
Toutes les propriétés animables ne sont pas égales en termes de performances. Le navigateur doit recalculer la mise en page à chaque image d'animation pour certaines propriétés — ce qui peut produire des ralentissements sur des appareils moins puissants.
/*
Propriétés les plus performantes — animées par le GPU,
sans recalcul du layout :
*/
transform: translate(), rotate(), scale()
opacity
/*
Propriétés à éviter dans les animations — recalculent le layout
à chaque image :
*/
width, height
padding, margin
top, left, right, bottom
font-size
Règle d'or des animations performantes : pour déplacer un élément, utilisez transform: translate() plutôt que top/left. Pour le faire apparaître/disparaître, utilisez opacity plutôt que display. Ces deux propriétés sont gérées directement par le GPU et n'affectent pas le flux du document.
La durée et la courbe de vitesse d'une transition sont aussi importantes que la propriété animée. Une transition trop lente est frustrante. Trop rapide, elle est imperceptible. Une courbe de vitesse mal choisie rend l'animation mécanique ou artificielle.
La durée s'exprime en secondes (s) ou millisecondes (ms). Elle n'existe pas de valeur universelle parfaite, mais des plages recommandées selon le type d'interaction.
/* Micro-interactions (hover, focus) : 150–300ms */
.btn { transition: background-color 0.2s ease; }
/* Apparitions, disparitions de panneaux : 250–400ms */
.panneau { transition: transform 0.35s ease; }
/* Animations d'entrée de page, modales : 300–500ms */
.modale { transition: opacity 0.4s ease; }
/*
Au-delà de 500ms, une transition de navigation devient perceptible
comme un délai — l'utilisateur attend.
En dessous de 100ms, elle est quasi imperceptible.
*/
transition-timing-function).La fonction de timing définit la courbe de vitesse — comment la propriété évolue entre sa valeur de départ et sa valeur d'arrivée.
<!-- timing-functions.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="timing-functions.css">
<title>Timing functions</title>
</head>
<body>
<p class="instruction">Survolez la zone pour voir les différences de courbe.</p>
<div class="scene" id="scene">
<div class="ligne">
<span class="label">linear</span>
<div class="barre"><div class="balle linear">●</div></div>
</div>
<div class="ligne">
<span class="label">ease (défaut)</span>
<div class="barre"><div class="balle ease">●</div></div>
</div>
<div class="ligne">
<span class="label">ease-in</span>
<div class="barre"><div class="balle ease-in">●</div></div>
</div>
<div class="ligne">
<span class="label">ease-out</span>
<div class="barre"><div class="balle ease-out">●</div></div>
</div>
<div class="ligne">
<span class="label">ease-in-out</span>
<div class="barre"><div class="balle ease-in-out">●</div></div>
</div>
<div class="ligne">
<span class="label">cubic-bezier</span>
<div class="barre"><div class="balle custom">●</div></div>
</div>
</div>
</body>
</html>
/* timing-functions.css */
body { font-family: Arial, sans-serif; padding: 40px; background-color: #0d1117; color: #e6edf3; }
.instruction {
font-size: 0.875rem;
color: #8b949e;
margin-bottom: 30px;
}
.scene { max-width: 700px; }
.ligne {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 16px;
}
.label {
font-size: 0.8125rem;
color: #8b949e;
width: 130px;
flex-shrink: 0;
font-family: monospace;
}
.barre {
flex: 1;
height: 36px;
background-color: #161b22;
border: 1px solid #30363d;
border-radius: 4px;
position: relative;
overflow: hidden;
}
.balle {
position: absolute;
left: 8px;
top: 50%;
transform: translateY(-50%);
font-size: 18px;
color: #2E6DA4;
transition-property: left;
transition-duration: 1.2s;
}
/* Chaque balle a sa propre courbe de vitesse */
.linear { transition-timing-function: linear; }
.ease { transition-timing-function: ease; }
.ease-in { transition-timing-function: ease-in; }
.ease-out { transition-timing-function: ease-out; }
.ease-in-out { transition-timing-function: ease-in-out; }
/*
cubic-bezier(x1, y1, x2, y2) : courbe de Bézier personnalisée.
Les quatre valeurs définissent les poignées de contrôle de la courbe.
Outil interactif en ligne : cubic-bezier.com
*/
.custom { transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); color: #e8ff47; }
/* Au survol de la scène, toutes les balles se déplacent vers la droite */
.scene:hover .balle {
left: calc(100% - 28px);
}
| Valeur | Comportement | Usage typique |
|---|---|---|
linear |
Vitesse constante du début à la fin | Rotations continues, loaders |
ease |
Démarre vite, ralentit à la fin (défaut) | Transitions générales |
ease-in |
Démarre lentement, finit vite | Éléments qui disparaissent |
ease-out |
Démarre vite, finit lentement | Éléments qui apparaissent |
ease-in-out |
Lent au début et à la fin | Glissements, panneaux |
cubic-bezier() |
Courbe personnalisée | Effets élastiques, rebonds |
transition-delay).Le délai diffère le démarrage de la transition. Il s'exprime dans la même unité que la durée et se place en quatrième position dans la propriété raccourcie.
.element {
/* propriété durée timing délai */
transition: opacity 0.3s ease 0.1s;
}
/* Délais échelonnés pour des éléments en cascade */
.item:nth-child(1) { transition-delay: 0s; }
.item:nth-child(2) { transition-delay: 0.05s; }
.item:nth-child(3) { transition-delay: 0.1s; }
.item:nth-child(4) { transition-delay: 0.15s; }
/*
Chaque élément démarre sa transition légèrement après le précédent.
Cet effet "cascade" ou "stagger" est très utilisé pour les listes
et les grilles qui apparaissent à l'écran.
*/
Objectif : construire une barre de navigation avec un méga-menu qui s'affiche en douceur au survol d'un lien. Le méga-menu contient plusieurs colonnes de liens. L'exercice travaille les transitions sur opacity, visibility et transform, le choix des courbes de timing selon la direction de l'interaction (apparition vs disparition), et les délais échelonnés sur les colonnes du menu.
C'est un composant que vous retrouverez dans tout site e-commerce, portail ou application métier.
Consignes HTML :
mega-menu.html avec la balise <meta name="viewport">.<header class="header"> contenant une <div class="header-inner"> avec :
<span class="logo"> (texte : Nexus).<nav class="nav"> avec quatre <div class="nav-item">. Les trois premiers ont un <a class="nav-lien"> simple. Le quatrième a un <a class="nav-lien nav-lien-actif"> (texte : Solutions) et une <div class="mega-menu">.<div class="nav-actions"> avec un <a class="btn-nav"> (Démo gratuite)..mega-menu, créez trois <div class="mega-col">. Chaque colonne contient un <p class="mega-titre"> et trois <a class="mega-lien"> avec un <span class="mega-lien-titre"> et un <span class="mega-lien-desc">.<main class="page-hero"> avec un <h1> et un <p>.mega-menu.css.Consignes CSS :
Avant d'écrire les transitions, réfléchissez à la direction de chaque interaction : le menu apparaît (entrée) ou disparaît (sortie) ? Quelle courbe de timing correspond à chaque cas ?
Header : fixe en haut de la page, fond sombre, hauteur définie. Le .nav-item est le déclencheur de positionnement — c'est lui qui doit avoir position: relative pour que le méga-menu se positionne par rapport à lui.
Méga-menu au repos : invisible mais présent dans le DOM (pas de display: none). Trouvez la combinaison de propriétés qui le rend invisible et l'empêche d'intercepter les clics, tout en permettant une transition fluide à l'apparition.
Méga-menu à l'apparition : lorsque l'utilisateur survole .nav-item, le menu apparaît avec un fondu et un léger glissement vers le bas. Choisissez une courbe de timing adaptée à une entrée. Les trois colonnes apparaissent avec des délais échelonnés — la première immédiatement, les suivantes légèrement après.
Méga-menu à la disparition : quand la souris quitte .nav-item, le menu disparaît. La courbe de timing doit être différente de l'apparition. Le délai échelonné s'inverse : sans délai sur le conteneur pour que la disparition soit réactive.
Contenu du menu : les .mega-col ont chacune un titre de catégorie et des liens avec un titre et une courte description sur deux lignes. Les liens ont un léger fond au survol, lui-même avec une transition courte.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="mega-menu.css">
<title>Mega menu — Nexus</title>
</head>
<body>
<header class="header">
<div class="header-inner">
<span class="logo">Nexus</span>
<nav class="nav">
<div class="nav-item">
<a href="#" class="nav-lien">Produits</a>
</div>
<div class="nav-item">
<a href="#" class="nav-lien">Tarifs</a>
</div>
<div class="nav-item">
<a href="#" class="nav-lien">Ressources</a>
</div>
<!-- Élément déclencheur du méga-menu -->
<div class="nav-item nav-item-menu">
<a href="#" class="nav-lien nav-lien-actif">
Solutions
<span class="fleche">▾</span>
</a>
<div class="mega-menu">
<div class="mega-col">
<p class="mega-titre">Par secteur</p>
<a href="#" class="mega-lien">
<span class="mega-lien-titre">Finance</span>
<span class="mega-lien-desc">Conformité, reporting et audit automatisés</span>
</a>
<a href="#" class="mega-lien">
<span class="mega-lien-titre">Santé</span>
<span class="mega-lien-desc">Gestion de données patients et interopérabilité</span>
</a>
<a href="#" class="mega-lien">
<span class="mega-lien-titre">Industrie</span>
<span class="mega-lien-desc">Suivi de production et maintenance prédictive</span>
</a>
</div>
<div class="mega-col">
<p class="mega-titre">Par équipe</p>
<a href="#" class="mega-lien">
<span class="mega-lien-titre">Direction</span>
<span class="mega-lien-desc">Tableaux de bord exécutifs en temps réel</span>
</a>
<a href="#" class="mega-lien">
<span class="mega-lien-titre">Développeurs</span>
<span class="mega-lien-desc">API REST, webhooks et documentation complète</span>
</a>
<a href="#" class="mega-lien">
<span class="mega-lien-titre">Marketing</span>
<span class="mega-lien-desc">Segmentation, campagnes et analytics intégrés</span>
</a>
</div>
<div class="mega-col">
<p class="mega-titre">À la une</p>
<a href="#" class="mega-lien">
<span class="mega-lien-titre">Nexus AI</span>
<span class="mega-lien-desc">Intelligence artificielle embarquée dans votre workflow</span>
</a>
<a href="#" class="mega-lien">
<span class="mega-lien-titre">Nexus Connect</span>
<span class="mega-lien-desc">Plus de 200 intégrations natives avec vos outils</span>
</a>
<a href="#" class="mega-lien">
<span class="mega-lien-titre">Migration guidée</span>
<span class="mega-lien-desc">Passez d'un autre outil en moins de 48 heures</span>
</a>
</div>
</div>
</div>
</nav>
<div class="nav-actions">
<a href="#" class="btn-nav">Démo gratuite</a>
</div>
</div>
</header>
<main class="page-hero">
<h1>La plateforme qui connecte vos équipes.</h1>
<p>Scrollez ou survolez "Solutions" dans la navigation pour voir le méga-menu.</p>
</main>
</body>
</html>
/* mega-menu.css */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
background-color: #f0f4f8;
color: #1a1a2e;
}
/* =====================
HEADER
===================== */
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background-color: #0d1117;
border-bottom: 1px solid #30363d;
}
.header-inner {
max-width: 1140px;
margin: 0 auto;
display: flex;
align-items: center;
height: 60px;
padding: 0 24px;
gap: 32px;
}
.logo {
font-size: 1.25rem;
font-weight: bold;
letter-spacing: 2px;
color: #e8ff47;
flex-shrink: 0;
}
/* =====================
NAVIGATION
===================== */
.nav {
display: flex;
align-items: stretch; /* Les nav-item s'étirent sur toute la hauteur du header */
gap: 2px;
flex: 1;
}
/*
position: relative sur .nav-item :
le méga-menu se positionnera par rapport à lui,
pas par rapport au header ou à la page.
*/
.nav-item {
position: relative;
display: flex;
align-items: center;
}
.nav-lien {
display: flex;
align-items: center;
gap: 4px;
padding: 0 14px;
color: rgba(255, 255, 255, 0.65);
text-decoration: none;
font-size: 0.9rem;
height: 100%;
border-radius: 4px;
transition: color 0.2s ease, background-color 0.2s ease;
}
.nav-lien:hover,
.nav-lien-actif {
color: #ffffff;
background-color: rgba(255, 255, 255, 0.06);
}
.fleche {
font-size: 0.7rem;
opacity: 0.6;
transition: transform 0.2s ease;
}
.nav-item-menu:hover .fleche {
transform: rotate(180deg);
}
.nav-actions {
margin-left: auto;
flex-shrink: 0;
}
.btn-nav {
display: inline-block;
background-color: #e8ff47;
color: #0d1117;
padding: 8px 18px;
text-decoration: none;
font-weight: bold;
font-size: 0.875rem;
border-radius: 4px;
transition: background-color 0.2s ease;
}
.btn-nav:hover { background-color: #d4eb30; }
/* =====================
MÉGA-MENU — ÉTAT DE REPOS
===================== */
.mega-menu {
position: absolute;
top: calc(100% + 8px); /* 8px sous le bas du nav-item */
left: 0;
width: 680px;
background-color: #161b22;
border: 1px solid #30363d;
border-radius: 10px;
padding: 24px;
display: flex;
gap: 0;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
/*
Invisible mais présent dans le DOM — permet la transition.
opacity: 0 rend invisible.
visibility: hidden empêche les clics sur les liens masqués.
transform: translateY(-8px) décale le menu vers le haut
pour préparer le glissement d'entrée.
*/
opacity: 0;
visibility: hidden;
transform: translateY(-8px);
/*
Transitions de SORTIE :
ease-in pour la sortie — accélère vers la disparition.
Pas de délai pour que la disparition soit réactive.
*/
transition:
opacity 0.2s ease-in,
visibility 0.2s ease-in,
transform 0.2s ease-in;
}
/* =====================
MÉGA-MENU — APPARITION
===================== */
.nav-item-menu:hover .mega-menu {
opacity: 1;
visibility: visible;
transform: translateY(0);
/*
Transitions d'ENTRÉE :
ease-out pour l'entrée — démarre vite, ralentit en arrivant.
Légèrement plus long que la sortie pour que le contenu
soit lisible avant la fin de l'animation.
*/
transition:
opacity 0.25s ease-out,
visibility 0.25s ease-out,
transform 0.25s ease-out;
}
/* =====================
COLONNES DU MÉGA-MENU
===================== */
.mega-col {
flex: 1;
padding: 0 20px;
border-right: 1px solid #30363d;
/*
État de repos : légèrement décalé vers le haut et transparent.
Chaque colonne prépare son propre glissement.
*/
opacity: 0;
transform: translateY(-6px);
transition:
opacity 0.2s ease-in,
transform 0.2s ease-in;
}
.mega-col:last-child {
border-right: none;
}
/*
Au survol : les colonnes apparaissent avec un délai échelonné.
La première colonne part immédiatement.
Les suivantes démarrent légèrement après — effet cascade.
*/
.nav-item-menu:hover .mega-col {
opacity: 1;
transform: translateY(0);
transition:
opacity 0.3s ease-out,
transform 0.3s ease-out;
}
.nav-item-menu:hover .mega-col:nth-child(1) { transition-delay: 0.05s; }
.nav-item-menu:hover .mega-col:nth-child(2) { transition-delay: 0.1s; }
.nav-item-menu:hover .mega-col:nth-child(3) { transition-delay: 0.15s; }
/* =====================
CONTENU DES COLONNES
===================== */
.mega-titre {
font-size: 0.6875rem;
text-transform: uppercase;
letter-spacing: 1.5px;
color: #8b949e;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #30363d;
}
.mega-lien {
display: flex;
flex-direction: column;
gap: 2px;
padding: 10px 8px;
border-radius: 6px;
text-decoration: none;
margin-bottom: 2px;
transition: background-color 0.15s ease;
}
.mega-lien:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.mega-lien-titre {
font-size: 0.875rem;
font-weight: bold;
color: #e6edf3;
}
.mega-lien-desc {
font-size: 0.75rem;
color: #8b949e;
line-height: 1.4;
}
/* =====================
PAGE HERO (fond de page)
===================== */
.page-hero {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 100px 24px 60px;
background: linear-gradient(160deg, #f0f4f8 60%, #e8f0fe);
}
.page-hero h1 {
font-size: clamp(1.75rem, 4vw, 3rem);
color: #1a1a2e;
margin-bottom: 16px;
max-width: 600px;
}
.page-hero p {
font-size: 1rem;
color: #666;
}
opacity + visibility plutôt que display: none : c'est la combinaison fondamentale pour les menus animés. opacity: 0 seul rend invisible mais l'élément continue d'intercepter les clics — les liens restent cliquables même masqués, ce qui est un problème d'accessibilité et d'UX. visibility: hidden empêche les interactions. La différence avec display: none est que visibility peut être animée (enfin, elle est discrète — elle bascule instantanément) pendant que opacity se déroule en douceur.
Courbes de timing différentes à l'entrée et à la sortie : ease-out à l'apparition (le menu part vite et s'installe doucement), ease-in à la disparition (le menu prend de l'élan pour partir). Ce n'est pas qu'une question d'esthétique — c'est une règle de motion design qui imite les objets physiques. Une disparition ease-out serait frustrante car elle freinerait en sortant, donnant l'impression que le menu "hésite" à partir.
Les délais échelonnés uniquement à l'apparition : les colonnes apparaissent avec 0.05s, 0.1s et 0.15s de délai. À la disparition, aucun délai n'est appliqué — le menu s'efface d'un bloc, rapidement. Ajouter des délais échelonnés à la disparition serait pénible : l'utilisateur devrait attendre que chaque colonne finisse de disparaître avant que le menu soit totalement fermé.
position: relative sur .nav-item, pas sur .nav : le méga-menu se positionne par rapport à son ancêtre le plus proche en position: relative ou absolute. Si la propriété était sur .nav, le menu s'alignerait sur le groupe entier de liens. Sur .nav-item, il s'aligne précisément sous le lien "Solutions".
Erreur fréquente : définir la même transition dans l'état de repos et au survol. En faisant cela, les transitions d'entrée et de sortie seraient identiques. En écrivant deux blocs transition différents — un dans l'état de repos (sortie) et un au survol (entrée) — on contrôle indépendamment les deux directions.
Les transitions animent le passage d'un état A vers un état B, déclenchées par une interaction. Les animations CSS vont plus loin : elles peuvent enchaîner un nombre illimité d'étapes intermédiaires, se jouer automatiquement sans interaction, se répéter, alterner, et être contrôlées finement dans leur déroulement. Là où la transition est réactive, l'animation est autonome.
Une animation CSS fonctionne en deux temps distincts. D'abord, on définit l'animation avec @keyframes — c'est le scénario, indépendant de tout élément. Ensuite, on applique cette animation à un élément avec la propriété animation. Ces deux étapes sont séparées et peuvent être dans n'importe quel ordre dans le fichier CSS.
/* Étape 1 : définir l'animation — le scénario */
@keyframes fondu-entree {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Étape 2 : appliquer l'animation à un élément */
.titre {
animation: fondu-entree 0.6s ease-out;
}
Syntaxe complète de la propriété animation.
.element {
animation: nom durée timing-function délai itérations direction fill-mode état;
}
/* Les valeurs les plus courantes */
.element {
animation-name: fondu-entree;
animation-duration: 0.6s;
animation-timing-function: ease-out;
animation-delay: 0.2s;
animation-iteration-count: 1; /* Nombre de répétitions, ou infinite */
animation-direction: normal; /* normal, reverse, alternate, alternate-reverse */
animation-fill-mode: forwards; /* none, forwards, backwards, both */
animation-play-state: running; /* running ou paused */
}
/* Raccourci équivalent */
.element {
animation: fondu-entree 0.6s ease-out 0.2s 1 normal forwards;
}
@keyframes définit les étapes de l'animation. Chaque étape est un pourcentage de la durée totale — 0% est le début, 100% est la fin. from est un alias de 0% et to un alias de 100%.
/* Forme simple : from / to */
@keyframes apparition {
from { opacity: 0; }
to { opacity: 1; }
}
/* Forme équivalente avec pourcentages */
@keyframes apparition {
0% { opacity: 0; }
100% { opacity: 1; }
}
/* Pulsation : grossit puis revient */
@keyframes pulsation {
0% { transform: scale(1); }
50% { transform: scale(1.12); }
100% { transform: scale(1); }
}
/* Rebond : tombe, rebondit, s'immobilise */
@keyframes rebond {
0% { transform: translateY(0); animation-timing-function: ease-in; }
60% { transform: translateY(40px); animation-timing-function: ease-out; }
80% { transform: translateY(10px); animation-timing-function: ease-in; }
100% { transform: translateY(0); }
}
@keyframes entree-gauche {
from {
opacity: 0;
transform: translateX(-40px);
filter: blur(4px);
}
to {
opacity: 1;
transform: translateX(0);
filter: blur(0);
}
}
animation-timing-function peut être défini dans chaque étape de @keyframes pour contrôler la courbe de vitesse entre cette étape et la suivante.
@keyframes saut {
0% {
transform: translateY(0);
animation-timing-function: ease-out; /* Monte en ralentissant */
}
45% {
transform: translateY(-60px);
animation-timing-function: ease-in; /* Redescend en accélérant */
}
100% { transform: translateY(0); }
}
Voici un exemple HTML+CSS complet illustrant plusieurs animations à étapes multiples :
<!-- keyframes-etapes.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="keyframes-etapes.css">
<title>@keyframes — étapes multiples</title>
</head>
<body>
<div class="scene">
<div class="demo">
<div class="boite pulsation">Pulsation</div>
</div>
<div class="demo">
<div class="boite rotation-continue">Rotation</div>
</div>
<div class="demo">
<div class="boite saut">Saut</div>
</div>
<div class="demo">
<div class="boite fondu">Fondu</div>
</div>
</div>
</body>
</html>
/* keyframes-etapes.css */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: Arial, sans-serif;
background-color: #0d1117;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 40px;
}
.scene {
display: flex;
gap: 40px;
flex-wrap: wrap;
justify-content: center;
}
.demo {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
.boite {
width: 100px;
height: 100px;
border-radius: 12px;
background-color: #2E6DA4;
color: #ffffff;
font-size: 0.75rem;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
/* --- Pulsation : scale 1 → 1.15 → 1 en boucle --- */
@keyframes pulsation {
0%, 100% { transform: scale(1); background-color: #2E6DA4; }
50% { transform: scale(1.15); background-color: #e8ff47; color: #0d1117; }
}
.pulsation {
animation: pulsation 1.6s ease-in-out infinite;
}
/* --- Rotation continue --- */
@keyframes rotation {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.rotation-continue {
border-radius: 50%;
animation: rotation 2s linear infinite;
}
/* --- Saut avec rebond --- */
@keyframes saut {
0% { transform: translateY(0); animation-timing-function: ease-out; }
40% { transform: translateY(-50px); animation-timing-function: ease-in; }
70% { transform: translateY(0); animation-timing-function: ease-out; }
85% { transform: translateY(-14px); animation-timing-function: ease-in; }
100% { transform: translateY(0); }
}
.saut {
background-color: #C0392B;
animation: saut 1.2s ease infinite;
animation-delay: 0.4s;
}
/* --- Fondu entrant en boucle --- */
@keyframes fondu {
0%, 100% { opacity: 0; transform: scale(0.85); }
50% { opacity: 1; transform: scale(1); }
}
.fondu {
background-color: #1a3a5c;
border: 1px solid #2e6da4;
animation: fondu 2s ease-in-out infinite;
animation-delay: 0.8s;
}
animation-iteration-count — nombre de répétitions..element { animation-iteration-count: 1; } /* Une seule fois (défaut) */
.element { animation-iteration-count: 3; } /* Trois fois */
.element { animation-iteration-count: infinite; } /* En boucle permanente */
.element { animation-iteration-count: 1.5; } /* Une fois et demie */
animation-direction — sens de lecture..element { animation-direction: normal; } /* 0% → 100% à chaque cycle */
.element { animation-direction: reverse; } /* 100% → 0% à chaque cycle */
.element { animation-direction: alternate; } /* Alterne : 0%→100%, 100%→0%... */
.element { animation-direction: alternate-reverse;} /* Alterne en commençant à 100% */
alternate est particulièrement utile pour les animations de va-et-vient fluides : l'animation joue en avant, puis en arrière, en boucle, sans saccade au point de raccord.
/* Oscillation douce avec alternate — bien plus fluide qu'un keyframe 0%→50%→100% */
@keyframes oscillation {
from { transform: translateX(0); }
to { transform: translateX(30px); }
}
.pendule {
animation: oscillation 0.8s ease-in-out infinite alternate;
}
animation-fill-mode — état avant et après.Par défaut, un élément revient à son état CSS d'origine une fois l'animation terminée. animation-fill-mode contrôle ce comportement.
.element { animation-fill-mode: none; } /* Retour à l'état original après (défaut) */
.element { animation-fill-mode: forwards; } /* Reste sur le dernier keyframe après la fin */
.element { animation-fill-mode: backwards; } /* Applique le premier keyframe pendant le délai */
.element { animation-fill-mode: both; } /* Combine forwards et backwards */
/*
Cas typique : animation d'entrée.
Sans forwards, l'élément redeviendrait opacity: 0 après l'animation.
Avec forwards, il reste visible à l'état du dernier keyframe.
*/
@keyframes entree {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.element {
opacity: 0; /* État initial avant l'animation */
animation: entree 0.5s ease-out forwards;
}
animation-play-state — pause et reprise..element {
animation: rotation 2s linear infinite;
}
.element:hover {
animation-play-state: paused; /* L'animation se fige au survol */
}
@keyframes entree-liste {
from { opacity: 0; transform: translateX(-20px); }
to { opacity: 1; transform: translateX(0); }
}
.item {
opacity: 0;
animation: entree-liste 0.4s ease-out forwards;
}
.item:nth-child(1) { animation-delay: 0s; }
.item:nth-child(2) { animation-delay: 0.08s; }
.item:nth-child(3) { animation-delay: 0.16s; }
.item:nth-child(4) { animation-delay: 0.24s; }
.item:nth-child(5) { animation-delay: 0.32s; }
.element {
animation:
fondu-entree 0.5s ease-out forwards,
pulsation 2s ease-in-out 0.5s infinite;
/*
L'élément apparaît d'abord (fondu-entree, une fois),
puis pulse en boucle (pulsation, après 0.5s de délai).
*/
}
@keyframes pulsation {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
.badge {
animation: pulsation 1.5s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.badge {
animation: none;
}
}
Objectif : construire la page de présentation d'un jeu de science-fiction fictif. Le titre du jeu apparaît lettre par lettre à l'entrée. Les cartes de personnages se retournent en 3D au survol pour révéler leur fiche de statistiques. Une barre de chargement s'anime en boucle. Des étoiles scintillent en fond. Tout est réalisé en CSS pur, sans une seule ligne de JavaScript. Cet exercice est le récapitulatif de toute la partie 9 : transformations 2D et 3D, transitions avec courbes de timing différenciées, animations @keyframes à plusieurs étapes, fill-mode, délais échelonnés et respect de prefers-reduced-motion.
Consignes HTML :
jeu.html avec la balise <meta name="viewport">.<section class="hero"> contenant :
<div class="etoiles"> avec dix <div class="etoile"> numérotées (etoile-1 à etoile-10).<div class="hero-contenu"> avec un <div class="titre-jeu"> contenant cinq <span> — un par lettre du mot NEXUS — chacun avec une classe lettre et une classe numérotée (l-1 à l-5), puis un <p class="sous-titre"> et un <a class="btn-jouer"> (Jouer maintenant).<div class="barre-chargement"> avec un <div class="barre-remplissage"> et un <span class="barre-texte"> (CHARGEMENT...).<section class="personnages" id="personnages"> avec un <h2> et une <div class="grille-perso"> contenant trois <div class="carte-perso">. Chaque .carte-perso contient un <div class="carte-perso-inner"> avec :
<div class="perso-avant"> : une <div class="perso-avatar"> (une lettre), un <h3> (nom), un <p class="perso-classe"> (classe).<div class="perso-arriere"> : un <h3>, quatre <div class="stat-ligne"> — chacun avec un <span class="stat-nom"> et une <div class="stat-barre"> contenant un <div class="stat-remplissage"> avec une largeur définie en style inline — et un <a class="btn-choisir"> (Choisir).jeu.css.Consignes CSS — récapitulatif de la partie 9 :
Avant d'écrire quoi que ce soit, classez mentalement chaque élément animé : transformation seule ? Transition ? @keyframes ? Les trois combinés ?
Transformations (9.1) :
.etoile ont des tailles différentes grâce à scale() ou des width/height distinctes. Certaines sont légèrement inclinées avec rotate()..btn-jouer se soulève au survol avec translateY et s'agrandit avec scale — combinez les deux..carte-perso utilisent le retournement 3D : perspective sur le conteneur, rotateY(180deg) sur l'inner au survol, backface-visibility: hidden sur chaque face, transform: rotateY(180deg) sur la face arrière pour la pré-retourner.Transitions (9.2) :
ease-in-out — un mouvement de A vers B, symétrique..btn-jouer utilise ease-out à l'apparition et ease-in à la disparition. Écrivez deux blocs transition distincts : un dans le CSS de base (sortie), un dans :hover (entrée)..stat-remplissage partent de width: 0% et se remplissent vers leur valeur style inline au survol de la carte via une transition ease-out.Animations @keyframes (9.3) :
lettre-entree (opacity: 0 + translateY(-30px) → état final) avec un délai échelonné de 0s à 0.4s. Utilisez fill-mode: forwards..sous-titre apparaît avec fondu-entree après les lettres — calculez le délai pour qu'il démarre après la dernière lettre..btn-jouer apparaît aussi avec fondu-entree, puis enchaîne une pulsation douce en boucle. Combinez les deux animations avec des délais calculés..etoile scintillent avec une animation scintillement (opacity + léger scale) en infinite alternate. Donnez à chaque étoile une durée et un délai distincts..barre-remplissage s'anime de width: 0% à width: 100% en boucle infinite alternate.prefers-reduced-motion: reduce. Rendez immédiatement visibles les éléments qui démarraient avec opacity: 0.<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="jeu.css">
<title>NEXUS — Le jeu</title>
</head>
<body>
<section class="hero">
<div class="etoiles">
<div class="etoile etoile-1"></div>
<div class="etoile etoile-2"></div>
<div class="etoile etoile-3"></div>
<div class="etoile etoile-4"></div>
<div class="etoile etoile-5"></div>
<div class="etoile etoile-6"></div>
<div class="etoile etoile-7"></div>
<div class="etoile etoile-8"></div>
<div class="etoile etoile-9"></div>
<div class="etoile etoile-10"></div>
</div>
<div class="hero-contenu">
<div class="titre-jeu">
<span class="lettre l-1">N</span>
<span class="lettre l-2">E</span>
<span class="lettre l-3">X</span>
<span class="lettre l-4">U</span>
<span class="lettre l-5">S</span>
</div>
<p class="sous-titre">La guerre des mondes a commencé. Choisissez votre champion.</p>
<a href="#personnages" class="btn-jouer">Jouer maintenant</a>
</div>
<div class="barre-chargement">
<div class="barre-remplissage"></div>
<span class="barre-texte">CHARGEMENT...</span>
</div>
</section>
<section class="personnages" id="personnages">
<h2>Choisissez votre personnage</h2>
<div class="grille-perso">
<div class="carte-perso">
<div class="carte-perso-inner">
<div class="perso-avant av-1">
<div class="perso-avatar">K</div>
<h3>Kael</h3>
<p class="perso-classe">⚔️ Guerrier</p>
</div>
<div class="perso-arriere ar-1">
<h3>Kael</h3>
<div class="stat-ligne">
<span class="stat-nom">Force</span>
<div class="stat-barre"><div class="stat-remplissage" style="--w: 90%"></div></div>
</div>
<div class="stat-ligne">
<span class="stat-nom">Agilité</span>
<div class="stat-barre"><div class="stat-remplissage" style="--w: 55%"></div></div>
</div>
<div class="stat-ligne">
<span class="stat-nom">Magie</span>
<div class="stat-barre"><div class="stat-remplissage" style="--w: 20%"></div></div>
</div>
<div class="stat-ligne">
<span class="stat-nom">Endurance</span>
<div class="stat-barre"><div class="stat-remplissage" style="--w: 80%"></div></div>
</div>
<a href="#" class="btn-choisir">Choisir</a>
</div>
</div>
</div>
<div class="carte-perso">
<div class="carte-perso-inner">
<div class="perso-avant av-2">
<div class="perso-avatar">L</div>
<h3>Lyra</h3>
<p class="perso-classe">✨ Mage</p>
</div>
<div class="perso-arriere ar-2">
<h3>Lyra</h3>
<div class="stat-ligne">
<span class="stat-nom">Force</span>
<div class="stat-barre"><div class="stat-remplissage" style="--w: 25%"></div></div>
</div>
<div class="stat-ligne">
<span class="stat-nom">Agilité</span>
<div class="stat-barre"><div class="stat-remplissage" style="--w: 65%"></div></div>
</div>
<div class="stat-ligne">
<span class="stat-nom">Magie</span>
<div class="stat-barre"><div class="stat-remplissage" style="--w: 95%"></div></div>
</div>
<div class="stat-ligne">
<span class="stat-nom">Endurance</span>
<div class="stat-barre"><div class="stat-remplissage" style="--w: 45%"></div></div>
</div>
<a href="#" class="btn-choisir">Choisir</a>
</div>
</div>
</div>
<div class="carte-perso">
<div class="carte-perso-inner">
<div class="perso-avant av-3">
<div class="perso-avatar">Z</div>
<h3>Zyx</h3>
<p class="perso-classe">🗡️ Assassin</p>
</div>
<div class="perso-arriere ar-3">
<h3>Zyx</h3>
<div class="stat-ligne">
<span class="stat-nom">Force</span>
<div class="stat-barre"><div class="stat-remplissage" style="--w: 60%"></div></div>
</div>
<div class="stat-ligne">
<span class="stat-nom">Agilité</span>
<div class="stat-barre"><div class="stat-remplissage" style="--w: 95%"></div></div>
</div>
<div class="stat-ligne">
<span class="stat-nom">Magie</span>
<div class="stat-barre"><div class="stat-remplissage" style="--w: 40%"></div></div>
</div>
<div class="stat-ligne">
<span class="stat-nom">Endurance</span>
<div class="stat-barre"><div class="stat-remplissage" style="--w: 50%"></div></div>
</div>
<a href="#" class="btn-choisir">Choisir</a>
</div>
</div>
</div>
</div>
</section>
</body>
</html>
/* jeu.css */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
background-color: #03050d;
color: #e6edf3;
overflow-x: hidden;
}
/* ============================================================
ANIMATIONS @KEYFRAMES
============================================================ */
/* Apparition d'une lettre depuis le haut avec léger rebond */
@keyframes lettre-entree {
from {
opacity: 0;
transform: translateY(-30px) scale(0.8);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
/* Fondu simple — sous-titre et bouton */
@keyframes fondu-entree {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
/* Scintillement des étoiles */
@keyframes scintillement {
from { opacity: 0.15; transform: scale(1); }
to { opacity: 1; transform: scale(1.4); }
}
/* Remplissage de la barre de chargement */
@keyframes chargement {
from { width: 0%; }
to { width: 100%; }
}
/* Pulsation douce du bouton après son apparition */
@keyframes pulse-bouton {
0%, 100% { box-shadow: 0 0 0 0 rgba(232, 255, 71, 0.5); }
50% { box-shadow: 0 0 0 14px rgba(232, 255, 71, 0); }
}
/* ============================================================
HERO
============================================================ */
.hero {
position: relative;
min-height: 100vh;
min-height: 100dvh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 40px 24px 100px;
background: radial-gradient(ellipse at center, #0d1f3c 0%, #03050d 70%);
overflow: hidden;
}
/* ============================================================
ÉTOILES — transformations 2D (9.1) + animations (9.3)
============================================================ */
.etoile {
position: absolute;
background-color: #ffffff;
border-radius: 50%;
/*
Scintillement en boucle avec alternate (9.3) :
l'étoile passe de presque invisible à pleinement visible
puis revient en arrière, sans saccade au raccord.
*/
animation: scintillement ease-in-out infinite alternate;
}
/* Positions, tailles, durées et délais uniques pour chaque étoile */
.etoile-1 { width: 3px; height: 3px; top: 12%; left: 8%;
animation-duration: 2.1s; animation-delay: 0s; }
.etoile-2 { width: 2px; height: 2px; top: 25%; left: 20%;
animation-duration: 1.7s; animation-delay: 0.4s; }
.etoile-3 { width: 4px; height: 4px; top: 8%; left: 40%;
animation-duration: 2.8s; animation-delay: 0.9s;
transform: rotate(45deg); /* Transformation 2D (9.1) */ }
.etoile-4 { width: 2px; height: 2px; top: 35%; left: 65%;
animation-duration: 1.5s; animation-delay: 0.2s; }
.etoile-5 { width: 5px; height: 5px; top: 15%; left: 80%;
animation-duration: 2.4s; animation-delay: 0.7s; }
.etoile-6 { width: 2px; height: 2px; top: 55%; left: 10%;
animation-duration: 1.9s; animation-delay: 1.1s; }
.etoile-7 { width: 3px; height: 3px; top: 70%; left: 30%;
animation-duration: 2.2s; animation-delay: 0.5s; }
.etoile-8 { width: 4px; height: 4px; top: 60%; left: 75%;
animation-duration: 1.6s; animation-delay: 0.3s;
transform: rotate(30deg); }
.etoile-9 { width: 2px; height: 2px; top: 80%; left: 55%;
animation-duration: 2.6s; animation-delay: 0.8s; }
.etoile-10 { width: 3px; height: 3px; top: 45%; left: 90%;
animation-duration: 1.8s; animation-delay: 1.3s; }
/* ============================================================
TITRE LETTRE PAR LETTRE — animations (9.3)
============================================================ */
.titre-jeu {
display: flex;
justify-content: center;
gap: 8px;
margin-bottom: 28px;
}
.lettre {
display: inline-block;
font-size: clamp(4rem, 12vw, 8rem);
font-weight: bold;
color: #e8ff47;
text-shadow: 0 0 40px rgba(232, 255, 71, 0.5);
opacity: 0; /* Invisible avant l'animation */
/*
cubic-bezier avec y2 > 1 : léger rebond à l'arrivée.
C'est le cas d'usage typique de la courbe personnalisée (9.2).
forwards : la lettre reste visible après l'animation.
*/
animation: lettre-entree 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
/* Délais échelonnés — les lettres arrivent une par une (9.3) */
.l-1 { animation-delay: 0s; }
.l-2 { animation-delay: 0.1s; }
.l-3 { animation-delay: 0.2s; }
.l-4 { animation-delay: 0.3s; }
.l-5 { animation-delay: 0.4s; }
/* ============================================================
SOUS-TITRE
============================================================ */
.sous-titre {
font-size: clamp(1rem, 2vw, 1.25rem);
color: rgba(255, 255, 255, 0.65);
max-width: 480px;
line-height: 1.7;
margin-bottom: 36px;
opacity: 0;
/*
Démarre après la dernière lettre.
l-5 : délai 0.4s + durée 0.5s = 0.9s.
On ajoute 0.1s de respiration → délai de 1s.
*/
animation: fondu-entree 0.6s ease-out 1s forwards;
}
/* ============================================================
BOUTON JOUER — transitions (9.2) + animations (9.3)
============================================================ */
.btn-jouer {
display: inline-block;
background-color: #e8ff47;
color: #03050d;
padding: 16px 40px;
text-decoration: none;
font-size: 1.0625rem;
font-weight: bold;
letter-spacing: 1px;
text-transform: uppercase;
border-radius: 4px;
opacity: 0;
/*
Deux animations combinées (9.3) :
1. fondu-entree : apparaît après le sous-titre (délai 1.4s).
2. pulse-bouton : pulsation en boucle après l'apparition (délai 2s).
Calcul : 1.4s (délai) + 0.6s (durée) = 2s exactement.
*/
animation:
fondu-entree 0.6s ease-out 1.4s forwards,
pulse-bouton 2s ease-in-out 2s infinite;
/*
Transition de SORTIE — ease-in (part avec de l'élan) (9.2).
Le bloc :hover redéfinit avec ease-out pour l'entrée.
*/
transition: transform 0.2s ease-in, background-color 0.2s ease;
}
.btn-jouer:hover {
background-color: #d4eb30;
/* Transformation 2D : soulèvement + mise à l'échelle (9.1) */
transform: translateY(-4px) scale(1.04);
/* Transition d'ENTRÉE — ease-out (arrive vite, se pose) (9.2) */
transition: transform 0.2s ease-out, background-color 0.15s ease;
}
/* ============================================================
BARRE DE CHARGEMENT — animations (9.3)
============================================================ */
.barre-chargement {
position: absolute;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
width: min(400px, 80vw);
display: flex;
flex-direction: column;
align-items: stretch;
gap: 8px;
}
.barre-remplissage {
height: 3px;
width: 0%;
background: linear-gradient(to right, #2E6DA4, #e8ff47);
border-radius: 2px;
box-shadow: 0 0 8px rgba(232, 255, 71, 0.4);
/*
Se remplit puis se vide en boucle grâce à alternate (9.3).
ease-in-out : symétrique, naturel pour un va-et-vient.
*/
animation: chargement 2s ease-in-out infinite alternate;
}
.barre-texte {
font-size: 0.6875rem;
letter-spacing: 3px;
color: rgba(255, 255, 255, 0.3);
text-align: center;
}
/* ============================================================
SECTION PERSONNAGES
============================================================ */
.personnages {
padding: 80px 24px;
max-width: 1100px;
margin: 0 auto;
}
.personnages h2 {
text-align: center;
font-size: clamp(1.25rem, 3vw, 1.875rem);
color: rgba(255, 255, 255, 0.5);
text-transform: uppercase;
letter-spacing: 3px;
margin-bottom: 48px;
}
.grille-perso {
display: flex;
gap: 24px;
flex-wrap: wrap;
justify-content: center;
}
/* ============================================================
CARTES PERSONNAGES — transformations 3D (9.1) + transitions (9.2)
============================================================ */
/*
Scène 3D : perspective sur le conteneur, pas sur l'inner.
Hauteur fixe nécessaire pour que les deux faces s'alignent.
*/
.carte-perso {
flex: 1 1 280px;
max-width: 320px;
height: 360px;
perspective: 1000px; /* Plus petit = effet plus prononcé */
cursor: pointer;
}
/*
L'inner se retourne. preserve-3d : les faces vivent
dans l'espace 3D et ne sont pas aplaties.
*/
.carte-perso-inner {
position: relative;
width: 100%;
height: 100%;
transform-style: preserve-3d;
/*
ease-in-out : mouvement symétrique de A vers B.
Ni entrée ni sortie — juste un déplacement (9.2).
*/
transition: transform 0.7s ease-in-out;
}
.carte-perso:hover .carte-perso-inner {
transform: rotateY(180deg);
}
/* Les deux faces superposées en position absolue */
.perso-avant,
.perso-arriere {
position: absolute;
inset: 0;
border-radius: 12px;
/*
backface-visibility: hidden : cache la face quand
elle est dos à l'observateur (9.1 — transformations 3D).
*/
backface-visibility: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 28px;
gap: 12px;
}
/* --- FACE AVANT --- */
.perso-avant {
background-color: #0d1f3c;
border: 1px solid #1e3a5c;
}
.av-1 { border-color: #ff4d6d; }
.av-2 { border-color: #a259ff; }
.av-3 { border-color: #4dffd4; }
.perso-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
font-size: 2rem;
font-weight: bold;
line-height: 80px;
text-align: center;
color: #03050d;
margin-bottom: 8px;
/* Légère rotation décorative — transformation 2D (9.1) */
transform: rotate(-5deg);
}
.av-1 .perso-avatar { background-color: #ff4d6d; }
.av-2 .perso-avatar { background-color: #a259ff; transform: rotate(5deg); }
.av-3 .perso-avatar { background-color: #4dffd4; transform: rotate(-8deg); }
.perso-avant h3 {
font-size: 1.5rem;
letter-spacing: 2px;
text-transform: uppercase;
}
.perso-classe {
font-size: 0.875rem;
color: rgba(255, 255, 255, 0.5);
}
/* --- FACE ARRIÈRE --- */
.perso-arriere {
background-color: #111827;
border: 1px solid #30363d;
/*
Pré-retournée de 180° (9.1).
Quand l'inner fait son demi-tour, elle revient à 0° et devient lisible.
*/
transform: rotateY(180deg);
justify-content: flex-start;
gap: 0;
}
.ar-1 { border-color: #ff4d6d; }
.ar-2 { border-color: #a259ff; }
.ar-3 { border-color: #4dffd4; }
.perso-arriere h3 {
font-size: 1.125rem;
text-transform: uppercase;
letter-spacing: 2px;
margin-bottom: 20px;
align-self: flex-start;
}
/* --- BARRES DE STATISTIQUES --- */
.stat-ligne {
width: 100%;
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.stat-nom {
font-size: 0.75rem;
color: rgba(255, 255, 255, 0.5);
width: 70px;
flex-shrink: 0;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.stat-barre {
flex: 1;
height: 6px;
background-color: rgba(255, 255, 255, 0.08);
border-radius: 3px;
overflow: hidden;
}
.stat-remplissage {
height: 100%;
background: linear-gradient(to right, #2E6DA4, #4dffd4);
border-radius: 3px;
width: 0%; /* Démarre à 0 */
/*
Transition (9.2) : la barre se remplit à l'apparition de la face arrière.
ease-out : rapide au départ, se pose doucement.
Le délai de 0.35s laisse le retournement se terminer avant que
les barres commencent à se remplir.
*/
transition: width 0.6s ease-out 0.35s;
}
/*
Au survol de la carte, les barres se remplissent vers la valeur
définie par la variable CSS --w en style inline dans le HTML.
*/
.carte-perso:hover .stat-remplissage {
width: var(--w, 50%);
}
.btn-choisir {
display: block;
margin-top: 16px;
padding: 10px 28px;
background-color: transparent;
border: 1px solid #e8ff47;
color: #e8ff47;
text-decoration: none;
font-size: 0.875rem;
font-weight: bold;
letter-spacing: 1px;
text-transform: uppercase;
border-radius: 4px;
text-align: center;
width: 100%;
/* Transition courte sur background et transform (9.2) */
transition: background-color 0.2s ease, transform 0.2s ease-out;
}
.btn-choisir:hover {
background-color: #e8ff47;
color: #03050d;
transform: scale(1.02);
}
/* ============================================================
ACCESSIBILITÉ — prefers-reduced-motion (9.3)
============================================================ */
@media (prefers-reduced-motion: reduce) {
/* Rendre visibles immédiatement les éléments masqués par opacity: 0 */
.lettre,
.sous-titre,
.btn-jouer {
animation: none;
opacity: 1;
transform: none;
}
/* Supprimer les animations automatiques de fond */
.etoile {
animation: none;
opacity: 0.5;
}
/* Barre statique plutôt qu'animée */
.barre-remplissage {
animation: none;
width: 65%;
}
/* Conserver le retournement des cartes — il est déclenché
par l'utilisateur, pas automatique */
}
Les lettres et cubic-bezier(0.34, 1.56, 0.64, 1) : la valeur 1.56 dépasse 1, ce qui crée un léger rebond : la lettre dépasse légèrement sa position finale puis revient. C'est exactement le cas d'usage de la courbe personnalisée évoqué en 9.2.3. ease-out aurait simplement freiné — cette courbe ajoute du caractère.
Les barres de stats et les variables CSS --w : les largeurs cibles sont définies via des propriétés personnalisées (--w: 90%) dans le style inline du HTML, puis lues en CSS avec var(--w). Au survol, la transition anime width de 0% vers var(--w). C'est une technique CSS pur qui évite tout JavaScript pour des données variables par élément.
Le délai 0.35s sur les transitions de barres : sans ce délai, les barres commenceraient à se remplir pendant que la carte est encore en train de se retourner — l'animation serait visible sur la face avant pendant le flip. En attendant 0.35s (soit environ la moitié de la durée du retournement de 0.7s), les barres ne démarrent que lorsque la face arrière est en bonne position.
Deux animations combinées sur .btn-jouer : fondu-entree (une fois, forwards) puis pulse-bouton (infinie). Le délai de pulse-bouton est 2s = 1.4s (délai de fondu-entree) + 0.6s (sa durée). La séquence entière est orchestrée sans JavaScript, uniquement avec des délais calculés.
prefers-reduced-motion et le retournement des cartes : on désactive les animations automatiques (étoiles, barre, entrées des lettres) mais on conserve la transition de retournement des cartes. Pourquoi ? Parce que cette interaction est déclenchée volontairement par l'utilisateur — ce n'est pas un mouvement subi. La recommandation WCAG est de supprimer les animations autonomes, pas les animations réactives à une action.