Les unités CSS enfin claires : px, em, rem, %, vw/vh
Une largeur en %, une police en rem, une hauteur en vh : on recopie ces unités dans ses premières feuilles de style sans trop savoir ce qu'elles mesurent, et ça tient. Jusqu'au jour où un texte imbriqué devient énorme sans raison, où un padding-top: 10% ne fait pas du tout 10 % de la hauteur attendue, ou où une bande en 100vw fait apparaître une barre de défilement horizontale. Ces trois surprises ont la même origine : chaque unité relative se réfère à quelque chose de précis, et ce quelque chose n'est pas toujours celui qu'on imagine.
Méthode habituelle de ce blog : chaque valeur citée ici a été mesurée pendant la rédaction, le 21 septembre 2026, dans Chromium 153.0.8010.12 (le navigateur de Playwright 1.63.0, même moteur que Chrome) piloté depuis Node 20.19.4 sur Windows 11. Les tailles sont lues avec getComputedStyle, c'est-à-dire la valeur que le navigateur a réellement calculée, pas celle que j'ai écrite. Si le vocabulaire des boîtes et des marges est encore flou, le modèle de boîte est le préalable utile à tout ce qui suit.
La seule question qui compte : relatif à quoi
Il n'y a pas d'unités faciles et d'unités compliquées. Il y a une unité absolue, px, et des unités relatives qui ont chacune un point d'ancrage différent. Retenir le point d'ancrage suffit à prévoir le résultat, et c'est tout ce que les sections suivantes détaillent, mesures à l'appui.
px : fixe dans la page, pas sur l'écran
Le pixel CSS est l'unité de référence : font-size: 16px donne 16 pixels, quel que soit le contexte. C'est sa force, et c'est aussi sa limite, comme la section sur rem le montrera.
Un point mérite d'être levé tout de suite, parce qu'il trompe beaucoup de débutants : un pixel CSS n'est pas un point de votre dalle. La spécification le définit comme un 96e de pouce, soit px = 1/96th of 1in dans CSS Values and Units Module Level 4, formulation reprise par MDN sous la forme 1px = 1in / 96 (page sur le type <length>, consultée le 21 septembre 2026). Sur un écran à haute densité, le navigateur dessine donc un pixel CSS avec plusieurs points physiques. Je l'ai vérifié en capturant un carré de 100 pixels CSS de côté, d'abord sur un écran simulé à densité 1, puis à densité 2 : le fichier PNG obtenu fait 100 par 100 points dans le premier cas, 200 par 200 dans le second. Le carré n'a pas changé de taille apparente, le navigateur a simplement eu deux fois plus de points pour le dessiner.
/* 100 pixels CSS, quelle que soit la densité de l'écran */
#carre {
width: 100px;
height: 100px;
}em : la taille du texte de l'élément, avec effet boule de neige
L'unité em vaut la taille de police calculée de l'élément. Sur une propriété autre que font-size, la règle est directe : dans une boîte réglée sur font-size: 20px, un padding: 1em donne un remplissage mesuré à 20 pixels sur les quatre côtés. Le rembourrage suit la taille du texte, ce qui est exactement ce qu'on veut sur un bouton dont l'air autour du libellé doit grandir avec le libellé.
#boite {
font-size: 20px;
padding: 1em; /* mesuré : 20px en haut comme à gauche */
}Sur la propriété font-size elle-même, la référence change : em vaut alors la taille de police héritée, donc celle du parent. C'est là que l'effet boule de neige apparaît. Avec une racine à 16 pixels et une règle font-size: 1.5em appliquée à des blocs imbriqués, j'ai mesuré 24 pixels au premier niveau, 36 au deuxième, 54 au troisième. Chaque niveau multiplie le précédent, et personne n'a écrit 54 nulle part.
html { font-size: 16px; }
.em div { font-size: 1.5em; } /* 24px, puis 36px, puis 54px en imbriqué */
.rem div { font-size: 1.5rem; } /* 24px aux trois niveaux */rem : la racine, donc le réglage du lecteur
L'unité rem se réfère toujours à la taille de police de l'élément racine, la balise html. Dans le même test imbriqué, 1.5rem donne 24 pixels aux trois niveaux : l'imbrication n'a plus aucun effet. C'est ce qui en fait l'unité par défaut raisonnable pour les tailles de texte et les espacements d'un site.
Il y a une deuxième raison, moins connue et bien plus importante. La taille de police de la racine n'est pas une constante du web : c'est un réglage du navigateur, que le lecteur peut changer dans ses préférences (dans Chrome, Paramètres puis Apparence, Taille de police). Pour le vérifier autrement qu'en croyant sur parole, j'ai lancé Chromium sur un profil utilisateur neuf dont la préférence de taille standard était posée à 24 pixels, puis j'ai mesuré deux paragraphes, l'un en font-size: 16px, l'autre en font-size: 1rem.
Profil par défaut racine 16px | paragraphe en px 16px | paragraphe en rem 16px
Réglage à 24 px racine 24px | paragraphe en px 16px | paragraphe en rem 24pxLe résultat est net : le texte en rem grandit avec le réglage, le texte en px reste figé à 16 pixels. Écrire toutes ses tailles de texte en pixels revient donc à ignorer une préférence que le lecteur a prise pour une raison, souvent une raison de confort visuel. Ce réglage a beau être discret, il ne coûte rien de le respecter : c'est le même chiffre divisé par 16, et mettre en forme le texte en CSS devient même plus simple à raisonner une fois que 1 vaut une taille de base.
Le pourcentage : sa référence change avec la propriété
Le pourcentage se calcule sur le bloc conteneur, mais pas toujours sur la même dimension de ce bloc. Sur width, l'intuition marche : dans un conteneur de 400 pixels de large, width: 50% est mesuré à 200 pixels. Sur les marges et le rembourrage, l'intuition tombe. J'ai placé un enfant dans un conteneur de 400 pixels de large sur 200 de haut, avec padding-top: 10% et margin-top: 10% : les deux valeurs calculées sont de 40 pixels, soit 10 % de la largeur, et non 20 pixels qui seraient 10 % de la hauteur.
#conteneur { width: 400px; height: 200px; }
#enfant {
padding-top: 10%; /* mesuré : 40px, soit 10% de la LARGEUR */
margin-top: 10%; /* mesuré : 40px également */
width: 50%; /* mesuré : 200px */
}Ce n'est pas une bizarrerie de Chromium mais la règle écrite : la spécification CSS Box Model Level 3, recommandation du W3C du 11 avril 2024, indique pour padding-top comme pour margin-top que les pourcentages se réfèrent à la largeur logique du bloc conteneur. Loin d'être un piège gratuit, c'est ce qui permet la vieille astuce du bloc à proportions fixes, où un padding-top en pourcentage réserve une hauteur proportionnelle à la largeur. Les autres façons de dimensionner une boîte sont détaillées dans dimensions et positionnement en CSS.
Dernier cas : sur font-size, le pourcentage se comporte exactement comme em. Dans un parent à 20 pixels, font-size: 150% et font-size: 1.5em donnent tous deux 30 pixels mesurés. Deux écritures, un seul résultat.
line-height : le cas où le nombre bat le pourcentage
Cette propriété mérite sa section parce qu'elle accepte quelque chose qu'aucune autre propriété de dimension n'accepte : un nombre sans unité. Et la différence avec le pourcentage ne se voit que chez les descendants. J'ai comparé deux parents identiques réglés sur font-size: 20px, l'un avec line-height: 1.5, l'autre avec line-height: 150%, chacun contenant un enfant à font-size: 40px.
line-height: 1.5 parent 30px | enfant à 40px : 60px
line-height: 150% parent 30px | enfant à 40px : 30pxLes deux parents sont à 30 pixels, mais l'enfant du second hérite de la valeur déjà calculée, 30 pixels, pour un texte de 40 pixels de haut : les lignes se chevauchent. Avec le nombre, c'est le facteur qui est hérité, et chaque descendant recalcule son interligne sur sa propre taille. D'où la règle simple : pour line-height, le nombre sans unité, toujours.
body { line-height: 1.5; } /* hérité comme facteur, jamais comme valeur figée */vw et vh : la fenêtre, la barre de défilement et le mobile
Les unités vw et vh valent un centième de la largeur et de la hauteur de la fenêtre d'affichage. Sur une fenêtre de 1000 pixels de large, 100vw est bien mesuré à 1000 pixels. Le piège est ailleurs : quand la page défile verticalement, la barre de défilement prend de la place, et 100vw l'ignore. Dans un Chromium à fenêtre visible, avec un contenu assez haut pour faire défiler, j'ai relevé window.innerWidth à 1000 mais document.documentElement.clientWidth à 985 : l'élément large de 100vw fait donc 1000 pixels dans un espace de 985, et le navigateur ajoute une barre de défilement horizontale pour 15 pixels de trop.
/* Le classique bandeau pleine largeur qui fait défiler la page horizontalement */
#bandeau { width: 100vw; }
/* Version sans surprise : la largeur du bloc conteneur, barre déduite */
#bandeau { width: 100%; }Sur mobile, c'est vh qui pose problème, parce que la hauteur utile change quand la barre d'adresse se rétracte au défilement. Le standard a répondu avec trois familles d'unités : svh pour la petite fenêtre, interfaces déployées, lvh pour la grande, interfaces rétractées, et dvh qui suit dynamiquement la hauteur du moment (CSS Values and Units Module Level 4, section des unités relatives à la fenêtre, consultée le 21 septembre 2026). La spécification précise que vh correspond à la grande fenêtre, ce qui explique le symptôme bien connu d'un bloc en 100vh dont le bas passe sous la barre d'adresse tant qu'elle est visible. Mesure honnête de ma part : sur mon banc de bureau, fenêtre de 600 pixels de haut, les quatre unités donnent exactement 600 pixels, parce qu'il n'y a aucune interface rétractable à prendre en compte. La différence ne se constate que sur un vrai navigateur mobile.
Côté disponibilité, ces trois familles ne sont plus une nouveauté : Chrome 108, Firefox 101 et Safari 15.4 les prennent en charge, d'après les données de compatibilité de MDN consultées le 21 septembre 2026. Pour un plein écran mobile, 100dvh est aujourd'hui le choix par défaut, et la logique des points de rupture qui va avec est traitée dans responsive design et media queries.
Ce que j'utilise, et quand
Voici le récapitulatif que j'aurais aimé lire en commençant. La colonne de droite n'est pas une loi, c'est le choix qui évite le plus de surprises dans un site ordinaire.
| Unité | Se réfère à | Quand l'utiliser |
|---|---|---|
px | rien, c'est la référence | bordures, ombres, petits décalages fixes |
em | la taille de police de l'élément | rembourrage d'un bouton, espacement lié au texte |
rem | la taille de police de la racine | tailles de texte, espacements généraux, points de rupture |
% | le bloc conteneur, selon la propriété | largeurs fluides, blocs à proportions fixes |
| nombre seul | la taille de police de l'élément | uniquement pour line-height, parmi les propriétés de dimension |
vw et vh | la fenêtre d'affichage | sections plein écran, avec dvh sur mobile |
Si un seul réflexe devait rester de cet article, ce serait celui-ci : devant une valeur qui ne donne pas ce que vous attendiez, ne changez pas l'unité au hasard, demandez-vous à quoi elle se réfère, puis vérifiez la valeur calculée dans l'onglet Éléments de votre navigateur. Le panneau des styles affiche la valeur que le navigateur a retenue, et c'est elle qui a raison. Une fois ce réflexe acquis, Flexbox et les mises en page fluides deviennent nettement moins mystérieux, parce qu'on cesse de deviner.