Créez des sites qui s'adaptent à toutes les tailles d'écran — du smartphone au grand écran. Media queries, design fluide, container queries : les techniques modernes du responsive.
Le responsive design n'est plus optionnel — c'est une exigence fondamentale. Plus de 60% du trafic web provient de mobiles. Google pénalise les sites non responsive dans son référencement.
| Pilier | Technique | Description |
|---|---|---|
| Media Queries | @media |
Appliquer des styles selon la taille/propriétés de l'écran |
| Layout fluide | %, vw, fr, min/max/clamp | Des grilles et conteneurs qui s'étirent |
| Images flexibles | max-width, object-fit | Images qui ne débordent jamais |
| Typographie fluide | clamp(), vw | Tailles de police adaptatives |
min-width) est recommandée car elle produit un code plus léger et plus performant.
/* Syntaxe de base */
@media (condition) {
/* Styles appliqués quand la condition est vraie */
}
/* Exemples */
@media (min-width: 768px) {
.conteneur {
padding: 2rem;
}
}
@media (max-width: 480px) {
.sidebar {
display: none;
}
}
/* ET — les deux conditions doivent être vraies */
@media (min-width: 768px) and (max-width: 1024px) {
/* Tablette uniquement */
}
/* OU — au moins une condition vraie */
@media (max-width: 480px), (orientation: portrait) {
/* Mobile OU portrait */
}
/* NOT — négation */
@media not print {
/* Tout sauf l'impression */
}
/* Imbrication (moderne) */
@media (min-width: 768px) {
.container { width: 90%; }
@media (min-width: 1024px) {
.container { width: 80%; }
}
}
| Feature | Description | Exemple |
|---|---|---|
width |
Largeur de la zone d'affichage | (min-width: 768px) |
height |
Hauteur de la zone d'affichage | (max-height: 600px) |
orientation |
Portrait ou landscape | (orientation: portrait) |
aspect-ratio |
Rapport largeur/hauteur | (aspect-ratio: 16/9) |
resolution |
Densité de pixels | (min-resolution: 2dppx) |
hover |
Support du survol | (hover: hover) |
pointer |
Précision du pointeur | (pointer: coarse) |
prefers-color-scheme |
Thème système | (prefers-color-scheme: dark) |
prefers-reduced-motion |
Réduction de mouvement | (prefers-reduced-motion: reduce) |
SYNTAXE DES MEDIA QUERIES
═══════════════════════════════════════════════
@media (type: feature: valeur) {
/* styles */
}
Types : all | screen | print
Features :
├── width / height / min-width / max-width
├── orientation (portrait | landscape)
├── aspect-ratio / min-aspect-ratio
├── resolution / min-resolution
├── hover (none | hover)
├── pointer (none | coarse | fine)
├── color-gamut
├── prefers-color-scheme
├── prefers-reduced-motion
└── ...
Opérateurs :
├── and → ET logique
├── , → OU logique
└── not → NÉGATION
Anatomie complète d'une media query
| Critère | Mobile First | Desktop First |
|---|---|---|
| Approche | min-width (augmenter) |
max-width (diminuer) |
| Performance | Meilleure (CSS de base = mobile) | Moins bonne (CSS de base = desktop) |
| Code | Plus propre, moins de overrides | Plus de redondance |
| Maintenance | Facile à étendre | Peut devenir complexe |
| Adoption | Recommandée par tous | Légacy, migration progressive |
/* ✅ MOBILE FIRST — Recommandé */
.card-grid {
display: grid;
grid-template-columns: 1fr; /* Mobile : 1 colonne */
gap: 1rem;
}
@media (min-width: 768px) {
.card-grid {
grid-template-columns: 1fr 1fr; /* Tablette : 2 colonnes */
}
}
@media (min-width: 1024px) {
.card-grid {
grid-template-columns: repeat(3, 1fr); /* Desktop : 3 colonnes */
}
}
/* ❌ DESKTOP FIRST — À éviter */
.card-grid {
grid-template-columns: repeat(3, 1fr); /* Desktop par défaut */
}
@media (max-width: 1023px) {
.card-grid {
grid-template-columns: 1fr 1fr; /* Tablette : override */
}
}
@media (max-width: 767px) {
.card-grid {
grid-template-columns: 1fr; /* Mobile : override encore */
}
}
/* Définir un conteneur */
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
/* Styles conditionnels selon la taille du conteneur */
@container sidebar (min-width: 300px) {
.widget {
display: flex;
gap: 1rem;
}
}
Développeuse Web Senior
Designer UX/UI
Note : Les container queries sont supportées dans tous les navigateurs modernes (Chrome 105+, Firefox 110+, Safari 16+).
clamp(), min(), max()) pour créer des éléments qui s'adaptent progressivement à la taille de l'écran, sans media query.
/* clamp(min, préféré, max) */
h1 {
font-size: clamp(1.5rem, 4vw, 3rem);
/* Minimum 1.5rem, idéal 4vw, maximum 3rem */
}
p {
font-size: clamp(0.875rem, 1.5vw, 1.05rem);
}
/* Échelle de typographie fluide */
:root {
--fs-sm: clamp(0.8rem, 0.17vw + 0.76rem, 0.89rem);
--fs-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
--fs-md: clamp(1.25rem, 0.61vw + 1.1rem, 1.58rem);
--fs-lg: clamp(1.56rem, 1vw + 1.31rem, 2.11rem);
--fs-xl: clamp(1.95rem, 1.56vw + 1.56rem, 2.81rem);
--fs-2xl: clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
}
/* Espaces qui grandissent avec l'écran */
.section {
padding: clamp(2rem, 5vw, 6rem) clamp(1rem, 3vw, 4rem);
}
.gap-fluide {
gap: clamp(1rem, 2vw, 2rem);
}
/* Conteneur centré avec max-width */
.conteneur {
width: min(90%, 1200px);
margin: 0 auto;
}
/* Grille fluide */
.grille {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
gap: clamp(1rem, 2vw, 2rem);
}
/* Sidebar fixe + contenu flexible */
.layout {
display: grid;
grid-template-columns: clamp(250px, 25vw, 300px) 1fr;
gap: 2rem;
}
/* Image qui ne déborde jamais */
img {
max-width: 100%;
height: auto;
}
/* Image avec ratio fixe */
.image-cadre {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover; /* Remplit en gardant le ratio */
object-fit: contain; /* Contient sans déformer */
}
/* Image-set pour résolution */
.background {
background-image: image-set(
"photo@1x.jpg" 1x,
"photo@2x.jpg" 2x
);
}
/* Picture pour art direction */
<picture>
<source media="(min-width: 1024px)" srcset="grand.jpg">
<source media="(min-width: 768px)" srcset="moyen.jpg">
<img src="petit.jpg" alt="Image responsive">
</picture>
/* Pattern hamburger classique */
.nav-links {
display: none; /* Caché sur mobile */
}
.nav-hamburger {
display: block; /* Visible sur mobile */
}
@media (min-width: 768px) {
.nav-links {
display: flex; /* Visible sur desktop */
}
.nav-hamburger {
display: none; /* Caché sur desktop */
}
}
/* Off-canvas menu */
@media (max-width: 767px) {
.nav-menu {
position: fixed;
top: 0;
left: -100%; /* Caché à gauche */
width: 280px;
height: 100vh;
transition: left 0.3s ease;
z-index: 1000;
}
.nav-menu.active {
left: 0; /* Apparaît */
}
}
/* Conteneur scrollable */
.table-responsive {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* Alternative : cartes sur mobile */
@media (max-width: 767px) {
table, thead, tbody, tr, td, th {
display: block;
}
thead { display: none; }
tr {
margin-bottom: 1rem;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 0.75rem;
}
td {
padding: 0.25rem 0;
text-align: right;
}
td::before {
content: attr(data-label);
float: left;
font-weight: 600;
text-align: left;
}
}
/* Mostly Fluid */
.grille {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr));
gap: 1rem;
}
/* Column Drop : les colonnes s'empilent sur mobile */
.layout {
display: grid;
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
.layout { grid-template-columns: 1fr 1fr; }
}
@media (min-width: 1024px) {
.layout { grid-template-columns: 1fr 2fr 1fr; }
}
/* Layout Shifter : le layout change radicalement */
.layout {
display: grid;
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
.layout {
grid-template-columns: 1fr 2fr;
}
}
@media (min-width: 1024px) {
.layout {
grid-template-columns: 200px 1fr 200px;
}
}
/* Le grid s'adapte automatiquement */
.grille-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
gap: 1.5rem;
padding: 1.5rem;
}
/* Résultat :
- > 1180px : 4 colonnes
- 880-1180px : 3 colonnes
- 580-880px : 2 colonnes
- < 580px : 1 colonne */
/* Base : mobile (1 colonne) */
.page {
display: grid;
grid-template-areas:
"header"
"main"
"sidebar"
"footer";
grid-template-columns: 1fr;
gap: 1rem;
padding: 1rem;
}
/* Tablette : 2 colonnes */
@media (min-width: 768px) {
.page {
grid-template-areas:
"header header"
"main sidebar"
"footer footer";
grid-template-columns: 1fr 250px;
padding: 2rem;
}
}
/* Desktop : sidebar plus large */
@media (min-width: 1024px) {
.page {
grid-template-columns: 1fr 300px;
max-width: 1200px;
margin: 0 auto;
}
}
.header { grid-area: header; }
.main { grid-area: main; }
.sidebar { grid-area: sidebar; }
.footer { grid-area: footer; }
<nav class="nav">
<div class="nav-logo">Logo</div>
<input type="checkbox" id="nav-toggle" class="nav-toggle-input">
<label for="nav-toggle" class="nav-hamburger">☰</label>
<ul class="nav-links">
<li><a href="#">Accueil</a></li>
<li><a href="#">Services</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
/* Base mobile : menu caché */
.nav { display: flex; justify-content: space-between; align-items: center; }
.nav-links { display: none; }
.nav-hamburger { display: block; }
/* Desktop : menu visible */
@media (min-width: 768px) {
.nav-links { display: flex; gap: 1.5rem; }
.nav-hamburger { display: none; }
}
/* Le composant s'adapte à son conteneur, pas au viewport */
.card-container {
container-type: inline-size;
}
.card {
display: flex;
flex-direction: column;
padding: 1rem;
}
.card img {
width: 100%;
aspect-ratio: 16/9;
object-fit: cover;
border-radius: 8px;
}
/* Quand le conteneur fait plus de 400px */
@container (min-width: 400px) {
.card {
flex-direction: row;
gap: 1rem;
}
.card img {
width: 200px;
aspect-ratio: auto;
}
}
<!-- ❌ Sans ce tag, les media queries ne marchent pas sur mobile -->
<head>
<title>Mon site</title>
</head>
<!-- ✅ Toujours inclure ce tag -->
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mon site</title>
</head>
/* ❌ Ne s'adapte pas */
.conteneur { width: 960px; }
.colonne { width: 300px; }
/* ✅ Utilise des unités relatives */
.conteneur { width: min(90%, 1200px); }
.colonne { width: 100%; }
/* ❌ 3 niveaux d'override */
.grille { grid-template-columns: 4fr 1fr; }
@media (max-width: 1023px) { .grille { grid-template-columns: 1fr 250px; } }
@media (max-width: 767px) { .grille { grid-template-columns: 1fr; } }
/* ✅ Mobile First : le code de base est déjà mobile */
.grille { grid-template-columns: 1fr; }
@media (min-width: 768px) { .grille { grid-template-columns: 1fr 250px; } }
@media (min-width: 1024px) { .grille { grid-template-columns: 4fr 1fr; } }
/* ❌ Cacher le contenu vital */
@media (max-width: 767px) {
.prix { display: none; } /* Le prix est caché ! */
.formulaire { display: none; } /* Pas de conversion ! */
}
/* ✅ Cacher uniquement le décoratif */
@media (max-width: 767px) {
.decoratif { display: none; }
.prix { font-size: 1.5rem; } /* Plus visible sur mobile */
}
/* ❌ Fragile et incomplet */
@media (max-width: 375px) { /* iPhone SE */ }
@media (max-width: 414px) { /* iPhone XR */ }
@media (max-width: 360px) { /* Galaxy S5 */ }
/* ✅ Basés sur le contenu, pas sur les appareils */
@media (max-width: 480px) { /* Petit écran */ }
@media (max-width: 768px) { /* Tablette */ }
@media (max-width: 1024px) { /* Desktop */ }
/* ❌ Liens et boutons trop petits sur mobile */
.nav-link {
padding: 4px 8px; /* Trop petit pour un doigt */
font-size: 0.7rem;
}
/* ✅ Taille minimale de 44x44px pour le tactile */
.nav-link {
padding: 12px 16px;
min-height: 44px;
display: flex;
align-items: center;
}
/* Définir les breakpoints dans des variables */
:root {
--bp-sm: 576px;
--bp-md: 768px;
--bp-lg: 1024px;
--bp-xl: 1200px;
}
/* Utiliser min-width avec ces variables */
@media (min-width: var(--bp-md)) {
/* Note : les variables ne marchent pas directement dans
les media queries, mais on peut utiliser des mixins
ou des conventions de nommage */
:root {
--fs-xs: clamp(0.7rem, 0.1vw + 0.68rem, 0.79rem);
--fs-sm: clamp(0.8rem, 0.17vw + 0.76rem, 0.89rem);
--fs-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
--fs-md: clamp(1.25rem, 0.61vw + 1.1rem, 1.58rem);
--fs-lg: clamp(1.56rem, 1vw + 1.31rem, 2.11rem);
--fs-xl: clamp(1.95rem, 1.56vw + 1.56rem, 2.81rem);
--fs-2xl: clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
}
auto-fit + minmax(min(280px, 100%), 1fr)1fr → 1fr 250pxclamp() pour padding + typojustify-content: space-between + hamburgermax-width: 100% + object-fitoverflow-x: auto ou transformation en cartes/* Toujours */
img { max-width: 100%; height: auto; }
/* Pour les images de fond */
.hero {
background-image: url("hero.jpg");
background-size: cover;
background-position: center;
}
/* Pour garder un ratio fixe */
.image-cadre {
aspect-ratio: 16 / 9;
object-fit: cover;
}
/* Pour servir différentes résolutions */
<picture>
<source media="(min-width: 1024px)" srcset="large.webp" type="image/webp">
<source media="(min-width: 768px)" srcset="medium.webp" type="image/webp">
<img src="small.jpg" alt="Description" loading="lazy">
</picture>
Défi 1 : Grille responsive
Créez une grille de cartes qui affiche 3 colonnes sur desktop, 2 sur tablette et 1 sur mobile, en utilisant Mobile First.
/* Votre code ici — Mobile First */
.grille {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
@media (min-width: 768px) {
.grille {
grid-template-columns: 1fr 1fr;
}
}
@media (min-width: 1024px) {
.grille {
grid-template-columns: repeat(3, 1fr);
}
}
Défi 2 : Typographie fluide
Créez une échelle de typographie fluide complète avec clamp() pour h1 à small, sans aucune media query.
Défi 3 : Navbar responsive
Créez une navbar avec un logo à gauche, des liens au centre et un bouton à droite. Sur mobile, les liens sont remplacés par un hamburger.
Défi 4 : Layout responsive complet
Créez un layout avec header, sidebar, contenu principal et footer. Sur mobile tout est en colonne, sur tablette sidebar + contenu, sur desktop sidebar fixe à gauche.
Défi 5 : Table responsive
Créez une table qui est scrollable horizontalement sur mobile OU qui se transforme en liste de cartes.
Projet 1 : Navbar responsive avec hamburger
Créez une navbar complète avec :
Projet 2 : Grille adaptative
Créez une grille qui s'adapte automatiquement :
auto-fit et minmax()clamp()Projet 3 : Cards responsives
Créez des cartes avec image, titre et description :
aspect-ratio et object-fitclamp()Projet 4 : Hero section responsive
Créez une hero section :
| Concept | Technique | Usage |
|---|---|---|
| Media queries | @media |
Styles conditionnels selon l'écran |
| Mobile First | min-width |
Approche recommandée |
| Container Queries | @container |
Composants adaptatifs |
| Typographie fluide | clamp() |
Tailles de police adaptatives |
| Layout fluide | %, vw, fr, min/max | Grilles et conteneurs flexibles |
| Images responsives | max-width, object-fit, picture | Images qui s'adaptent |
| Navigation responsive | Hamburger, off-canvas |
clamp(1rem, 3vw, 2rem) fait ?@media (min-width: 1024px)min-width (augmenter), Desktop First utilise max-width (diminuer)repeat(auto-fit, minmax(min(280px, 100%), 1fr))max-width: 100% + aspect-ratio + object-fit + <picture>and avec min-width et max-width : @media (min-width: 768px) and (max-width: 1023px)/* Conteneur responsive */
.conteneur { width: min(90%, 1200px); margin: 0 auto; }
/* Grille responsive */
.grille {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
gap: clamp(1rem, 2vw, 2rem);
}
/* Typographie fluide */
h1 { font-size: clamp(1.5rem, 4vw, 3rem); }
p { font-size: clamp(0.875rem, 1.5vw, 1rem); }
/* Media queries — Mobile First */
@media (min-width: 768px) { /* Tablette */ }
@media (min-width: 1024px) { /* Desktop */ }
@media (min-width: 1200px) { /* Large */ }
/* Image responsive */
img { max-width: 100%; height: auto; }
/* Image avec ratio fixe */
.img { aspect-ratio: 16/9; object-fit: cover; }
/* Container query */
.card-wrapper { container-type: inline-size; }
@container (min-width: 400px) { /* layout horizontal */ }
/* Sidebar responsive */
.layout {
display: grid;
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
.layout { grid-template-columns: 1fr 250px; }
}
/* Navbar responsive */
@media (max-width: 767px) {
.nav-links { display: none; }
.hamburger { display: block; }
}
/* Table responsive */
.table-wrap { overflow-x: auto; }