CSS 1 — modèle de boîte & flexbox
Tu écris du style tous les jours avec NativeWind — mais derrière chaque classe
p-4 ou flex-row se cache du CSS, le vrai. Les tests techniques de
stage exigent très souvent du CSS vanilla, sans Tailwind ni framework : il
faut savoir lire une feuille de style, expliquer le modèle de boîte, et centrer une div sans
chercher sur Google. Ce module pose ces fondations en s'appuyant en permanence sur ce que tu
connais déjà côté React Native.
- Tout est boîte. Chaque élément d'une page web est un rectangle avec un contenu, une marge intérieure (padding), une bordure et une marge extérieure (margin). Une fois qu'on voit les boîtes, on voit la page.
- Flexbox range les boîtes. C'est le même flexbox que tu utilises en React Native — à deux ou trois défauts près, et ces défauts sont précisément là où les devs mobiles se font piéger en passant au web. On va les nommer un par un.
Ce que « cascade » veut dire : plusieurs règles se disputent un élément
CSS signifie Cascading Style Sheets, et le mot qui fait la personnalité du langage est cascade. Le problème qu'elle résout : sur une page réelle, un même élément est presque toujours visé par plusieurs règles. Le style par défaut du navigateur dit que les liens sont bleus et soulignés, ta feuille dit qu'ils sont verts, une règle plus bas dit que les liens dans le menu sont blancs. Trois règles, un lien : qui gagne ? La cascade est l'algorithme qui tranche, et il tranche toujours, de façon déterministe. Quand un style « ne marche pas », il n'y a pas de mystère — une autre règle a gagné la dispute, et on peut trouver laquelle.
L'arbitrage se joue en trois temps. Un — l'origine : les styles par défaut du
navigateur (c'est pour ça qu'un <h1> est gros et gras sans qu'on l'ait
demandé) passent derrière ceux que toi tu écris. Deux — la
spécificité : entre deux de tes règles, la plus « précise » gagne. Trois —
l'ordre d'apparition : à spécificité égale, la règle écrite en dernier
l'emporte. Ce dernier point devrait te rappeler quelque chose : c'est la règle « la dernière
classe gagne » de tailwind-merge, vue dans le guide Halterofit —
twMerge imite en JavaScript ce que la cascade fait nativement.
La spécificité, maintenant. On la présente souvent avec une arithmétique obscure de points ; la version simple suffit pour lire du CSS et répondre en entrevue. Quatre niveaux de poids, du plus lourd au plus léger :
- Le style inline — écrit directement sur l'élément,
<div style="color: red">. Il bat tout le reste (hors cas exotiques). - L'id — le sélecteur
#header. Un id est unique dans la page, donc une règle sur un id est très ciblée : elle pèse lourd. - La classe — le sélecteur
.card. Moins ciblée qu'un id (une classe peut être posée sur cent éléments), donc moins lourde. - L'élément — le sélecteur
poua, qui vise tous les paragraphes ou tous les liens. Le plus générique, donc le plus léger.
L'intuition à garder : plus un sélecteur est ciblé, plus il gagne, et un sélecteur
composé cumule — .menu .lien (deux classes) bat .lien (une classe).
C'est tout ce qu'il faut pour lire 99 % des feuilles de style réelles.
Dernière chose, et elle explique beaucoup de CSS moderne : les développeurs
s'en tiennent presque exclusivement aux classes. Les ids et le style inline
sont des armes trop lourdes — une fois qu'une règle #header existe, la surcharger
demande un sélecteur encore plus lourd, puis un autre, et la feuille devient une course à
l'armement (avec au bout le tristement célèbre !important, qui force une règle à
gagner et rend le fichier ingérable). En restant sur des classes, tout le monde joue dans la
même catégorie de poids et c'est l'ordre d'écriture, simple et prévisible, qui départage.
Tailwind pousse la logique à l'extrême : que des classes, jamais d'id, jamais d'inline.
En React Native, tu n'as jamais eu à penser à tout ça : le style d'un composant est un objet qui s'applique à ce composant, point — pas de cascade, pas de spécificité, pas de règle qui « fuit ». C'est justement pour retrouver ce confort que le web a inventé l'« utility-first » et les composants : confiner le style pour ne plus subir la cascade globale. Comprendre la cascade, c'est comprendre pourquoi Tailwind existe.
Le titre de la carte s'obstine à rester gris, alors que la seconde règle est bien plus détaillée et écrite plus bas dans le fichier. De quelle couleur est-il, et qu'est-ce que ça t'apprend sur la spécificité ?
#promo .carte .titre { color: gray; }
.encart .carte .titre.actif { color: #2563eb; }
Gris : c'est la première règle qui gagne. La spécificité ne s'additionne pas d'un niveau de poids à l'autre — on compare d'abord le nombre d'ids, et seulement en cas d'égalité le nombre de classes, puis d'éléments. Un seul #promo écrase donc quatre classes, quel que soit leur nombre, et l'ordre d'écriture ne départage qu'à spécificité strictement égale.
C'est exactement la course à l'armement décrite plus haut : dès qu'un id entre dans une feuille de style, seul un autre id — ou un !important — peut le surcharger, et on s'enferme. D'où la discipline moderne, que Tailwind pousse à l'extrême : que des classes, tout le monde dans la même catégorie de poids, et c'est l'ordre d'écriture, simple et lisible, qui tranche.
Les sélecteurs essentiels, en lecture
Une règle CSS a toujours la même anatomie : un sélecteur (qui est visé ?),
puis un bloc de déclarations entre accolades, chacune une paire
propriété: valeur;. L'objectif ici n'est pas de réciter tous les sélecteurs — il y
en a des dizaines — mais de savoir lire ceux qui couvrent l'immense majorité
des feuilles réelles :
/* Élément : « tous les paragraphes » */
p { line-height: 1.6; }
/* Classe : « tout élément portant class="card" » — LE sélecteur du quotidien */
.card { border-radius: 8px; }
/* Id : « L'élément portant id="header" » — rare dans le CSS moderne */
#header { position: sticky; }
/* Descendant (un ESPACE) : « les .prix qui sont QUELQUE PART dans une .card,
enfant, petit-enfant, peu importe la profondeur » */
.card .prix { font-weight: 700; }
/* Enfant direct (un CHEVRON) : « les li directement enfants du .menu,
pas les li imbriqués plus profond » */
.menu > li { padding: 8px; }
/* États : au survol de la souris, au focus clavier */
.bouton:hover { background: #0f766e; }
.champ:focus { outline: 2px solid #0f766e; }
Six formes de sélecteurs. Si tu sais lire ça, tu sais lire l'essentiel de n'importe quelle feuille de style.
Trois précisions de lecture. D'abord l'espace contre le
chevron : a b (descendant) ratisse large — n'importe quel
b à n'importe quelle profondeur sous un a — tandis que
a > b (enfant direct) ne vise que le premier niveau. Quand un style semble
« traverser » toute une sous-arborescence, cherche un sélecteur descendant.
Ensuite les pseudo-classes comme :hover et :focus :
le deux-points signale un état, pas un élément différent. .bouton:hover
se lit « le .bouton, quand la souris est dessus » ; :focus, « quand il a le focus
clavier » — tu as vu son importance pour l'accessibilité au module précédent. Note qu'un état
de survol n'existe pas en mobile natif : il n'y a pas de souris.
Enfin les sélecteurs à double deux-points, ::before et
::after : des pseudo-éléments, un contenu fantôme que CSS insère avant ou
après le contenu réel, typiquement pour une décoration (puce maison, trait, icône) sans ajouter
de balise. En lecture, .lien::before { content: '→'; } se comprend « CSS dessine
une flèche juste avant le texte du lien ». Tu n'as pas besoin d'en écrire, juste de ne pas être
dérouté en en voyant.
Voici un extrait de feuille de style. Trois questions en dessous.
a { color: #2563eb; }
.menu a { color: white; text-decoration: none; }
.menu > li:hover { background: #1e293b; }
Questions : (1) De quelle couleur est un lien posé dans le corps de la page, hors du menu ? (2) De quelle couleur est un lien dans le menu, et pourquoi la première règle ne gagne-t-elle pas ? (3) Que se passe-t-il quand la souris survole un élément de liste directement enfant du menu ?
Voir le corrigé
(1) Bleu (#2563eb) : seule la règle a le vise.
(2) Blanc, et sans soulignement. Les deux règles visent le même lien,
mais .menu a (une classe + un élément) est plus spécifique
que a tout seul (un élément) : elle gagne la dispute. C'est la cascade en
action — spécificité d'abord, ordre ensuite.
(3) Son fond devient gris-bleu foncé (#1e293b) tant que la
souris est dessus. Le > précise : seulement les li de premier
niveau du menu, pas ceux d'un éventuel sous-menu imbriqué. Le :hover précise :
seulement pendant le survol.
Le modèle de boîte : content, padding, border, margin
Question d'entrevue numéro un du CSS, et à juste titre : c'est la fondation de tout le reste. Chaque élément d'une page — chaque paragraphe, chaque image, chaque div — est rendu comme un rectangle, et ce rectangle a quatre couches concentriques, de l'intérieur vers l'extérieur :
- Le contenu (content) : le texte, l'image, ce qu'il y a dedans.
- Le padding : la marge intérieure, l'espace entre le contenu et la bordure. Il porte la couleur de fond de l'élément — un fond coloré s'étend jusque sous le padding.
- La bordure (border) : le trait qui entoure, avec une épaisseur, un style et une couleur.
- La margin : la marge extérieure, l'espace entre cette boîte et ses voisines. Elle est toujours transparente — c'est du vide entre les boîtes.
L'analogie classique est le tableau encadré : le contenu est la toile, le padding le passe-partout, la bordure le cadre, la margin l'espace entre deux tableaux sur le mur. Cette distinction te sert déjà en React Native — même modèle, mêmes noms.
Le piège historique que RN n'a pas tient à une question :
« quand j'écris width: 180px, qu'est-ce qui mesure 180 pixels ? »
Le comportement d'origine, content-box, répond le contenu seul : padding
et bordure s'ajoutent par-dessus. Une carte à width: 180px avec
padding: 18px et une bordure de 3 px occupe en réalité
180 + 18 + 18 + 3 + 3 = 222 pixels. Personne ne raisonne comme ça : quand
tu dis « cette carte fait 180 px », tu penses au rectangle visible. Ce décalage a causé
des années de mises en page cassées au pixel près.
width: 180px, padding: 18px, bordure de 3 px — et ne
diffèrent que par box-sizing. La règle posée sous chaque boîte mesure la
largeur réellement occupée : 222 px en content-box, 180 en
border-box. En
border-box, c'est le contenu qui se comprime pour laisser la place.
La solution universellement adoptée : box-sizing: border-box. Avec elle,
width: 180px mesure le rectangle visible, bordure comprise — le contenu se
comprime pour laisser la place au padding et à la bordure à l'intérieur. C'est le
comportement intuitif, d'où le « reset » qui ouvre pratiquement tous les projets web (quelques
règles en tête de feuille pour neutraliser des défauts gênants du navigateur) :
/* Le reset le plus répandu du web : TOUT passe en border-box.
L'étoile * sélectionne tous les éléments. */
*, *::before, *::after {
box-sizing: border-box;
}
Trois lignes présentes au sommet de presque toutes les feuilles de style du monde. Quand tu lis un CSS de test technique, pars du principe qu'elles y sont — et vérifie.
En React Native, box-sizing n'existe pas, et pour cause : Yoga, le moteur de
layout de RN, se comporte déjà comme border-box. Ton intuition est
donc la bonne — il te suffit de savoir que le web, lui, doit la demander explicitement. Plus
largement, chaque classe NativeWind que tu tapes est la traduction d'une déclaration CSS de
ce module : p-4 = padding: 16px, border =
border-width: 1px. Le module sur les composants et le style du guide Halterofit
(composants.html) montrait comment ces classes
s'assemblent ; ici tu découvres ce qu'elles veulent dire en dessous.
Le web a une bizarrerie que RN n'a pas : le margin collapsing. Quand deux
blocs se suivent verticalement, leurs marges qui se touchent ne s'additionnent pas — elles
fusionnent, et seule la plus grande compte : margin-bottom: 24px suivi
de margin-top: 16px donne 24 px, pas 40. C'était pensé pour la typographie,
mais c'est déroutant la première fois qu'un espacement « disparaît ». Deux garde-fous : ça ne
concerne que les marges verticales de blocs dans le flux normal (la mise en
page par défaut, tant qu'aucun conteneur n'a demandé flexbox), et ça
n'arrive jamais dans un conteneur flex. Sache que ça existe pour ne pas
perdre vingt minutes le jour où ça te tombe dessus.
margin-bottom: 32px sur le premier, margin-top: 16px sur le
second. Seul le conteneur change, et les deux départs sont alignés pour que l'écart se
compare d'un coup d'œil. À gauche, dans le flux normal, les deux marges fusionnent et
seule la plus grande compte : 32 px. À droite, en display: flex, elles
s'additionnent : 48 px. Deux modes de mise en page, deux arithmétiques.
Deux blocs se suivent verticalement dans la page. Quel espace les sépare réellement ? Et si leur parent recevait display: flex; flex-direction: column ?
.intro { margin-bottom: 32px; }
.services { margin-top: 16px; }
32 px, pas 48. Dans le flux normal, deux marges verticales qui se touchent ne s'additionnent pas : elles fusionnent, et seule la plus grande compte. Le margin-top: 16px est intégralement avalé — il ne sert à rien tant que l'autre marge est plus grande, ce qui est la cause numéro un des espacements « qui ne bougent pas quand on les change ».
Avec un parent en display: flex : 48 px. La fusion n'existe que dans le flux normal ; dès qu'un conteneur passe en flex, ses enfants ne sont plus des blocs de ce flux et leurs marges s'additionnent comme ton intuition de React Native l'attend. Deux modes de mise en page, deux arithmétiques — d'où l'intérêt, en lisant du CSS, de repérer d'abord dans quel mode on se trouve.
display : block, inline, et le flux normal du document
Voici, conceptuellement, la plus grosse différence entre le web et React
Native. En RN, aucune question à se poser : toute View est positionnée par
flexbox, toujours. Sur le web, avant même que flexbox n'entre en scène, les éléments sont posés
par le flux normal du document — l'algorithme de mise en page par défaut,
hérité de l'époque où une page web était littéralement une page de texte. Le comportement de
chaque élément dans ce flux dépend de sa propriété display.
display: block : l'élément se comporte comme un paragraphe — il
prend toute la largeur disponible et force un retour à la ligne avant et après, si
bien que les blocs s'empilent verticalement. <div>, <p>,
<h1>, <section>, <ul> sont des blocs,
et acceptent width, height et marges verticales.
display: inline : l'élément se comporte comme un mot dans une
phrase. Il se glisse dans le fil du texte, ne provoque aucun retour à la ligne, et —
piège classique — ignore width, height et les marges
verticales : on ne donne pas une hauteur à un mot au milieu d'une phrase.
<a>, <span>, <strong>,
<code> sont inline par défaut.
display: inline-block : l'hybride — dans le fil du texte comme un
mot, mais acceptant largeur, hauteur et marges. Très utilisé pour les boutons et menus
horizontaux avant flexbox ; aujourd'hui surtout croisé dans du vieux code. Et
display: none : l'élément disparaît complètement, n'occupe plus
aucune place, comme s'il n'était pas dans le HTML — à distinguer de
visibility: hidden (invisible mais l'espace reste réservé) et, côté React, du
rendu conditionnel condition && <Composant /> qui retire carrément
l'élément de l'arbre.
Pourquoi cette taxonomie alors que « tout le monde utilise flexbox » ? D'abord parce que le
flux normal reste le défaut : tant que personne n'a écrit display: flex,
c'est lui qui commande, et une page de contenu textuel se met très bien en page avec lui seul.
Ensuite parce qu'il explique des comportements qui semblent aléatoires quand on vient du
mobile : « pourquoi ma div prend toute la largeur ? » (c'est un bloc), « pourquoi mon width est
ignoré ? » (c'est un inline), « pourquoi ces images ont un espace mystérieux entre elles ? »
(elles sont inline, donc traitées comme des mots séparés par des espaces). Le dev qui connaît
display lit ces situations en une seconde ; l'autre ajoute des règles au hasard.
Du texte avec un badge au milieu de la phrase.
Du texte avec un badge au milieu de la phrase.
Du texte avec un badge au milieu de la phrase.
width: 130px et height: 38px — et seul display
change. Le premier les ignore et se contente de la place de son texte : c'est le piège
du « pourquoi mon width ne fait rien ». Le deuxième les respecte tout en
restant dans le fil de la phrase. Le troisième force un retour à la ligne avant et après.
En React Native : flexbox partout, par défaut, sans opt-in. Sur le web :
flux normal par défaut, flexbox sur demande — chaque conteneur qui veut ranger ses
enfants au flexbox doit le déclarer avec display: flex. Quand tu lis du CSS,
repère ces display: flex : ce sont les endroits où la mise en page « passe en
mode RN ». Tout le reste vit dans le flux normal.
Le badge refuse de faire 80 px de large, sa hauteur est ignorée et l'espace au-dessus n'apparaît jamais — alors que les DevTools montrent bien la règle appliquée, sans rien de barré. Que se passe-t-il ?
<p>Consultation générale <span class="badge">Populaire</span></p>
.badge {
width: 80px;
height: 24px;
margin-top: 12px;
padding: 0 8px;
background: #fde68a;
}
Un <span> est inline par défaut : le navigateur le traite comme un mot au milieu d'une phrase. Et un mot n'a ni largeur imposée, ni hauteur, ni marge verticale — width, height, margin-top et margin-bottom sont donc purement ignorés. Pas d'erreur, pas d'avertissement, et la règle apparaît bien dans les DevTools : c'est ce qui rend le symptôme si déroutant quand on vient de React Native, où toute View est une boîte.
Ce qui marche quand même, et qui trompe : le padding horizontal et le background. D'où l'impression que « la règle est prise en compte à moitié ». Le correctif tient en une déclaration : display: inline-block pour rester dans le fil du texte tout en devenant une vraie boîte, ou display: flex/block si le badge doit vivre sur sa propre ligne.
Flexbox : le conteneur range, les enfants négocient
On arrive en terrain connu : chaque flex-row, chaque items-center,
chaque justify-between de NativeWind, c'est du flexbox. Le principe, pour le
redire proprement une fois : on pose display: flex sur un
conteneur, et ses enfants directs deviennent des « items
flexibles » rangés le long d'un axe. Les propriétés se répartissent en deux familles selon
où on les écrit — c'est la seule chose à ne jamais confondre, et le tableau te la
donne d'un coup d'œil.
Sur le CONTENEUR (celui qui porte display: flex) | Ce qu'elle décide |
|---|---|
flex-direction |
L'axe principal : row (défaut web) ou column, plus les
variantes -reverse. |
justify-content |
Distribue les enfants le long de l'axe principal : flex-start
(défaut), center, flex-end, space-between
(premier et dernier aux bords — le patron des barres d'en-tête),
space-around, space-evenly. |
align-items |
Aligne sur l'axe croisé, perpendiculaire : stretch (défaut des
deux côtés), center, flex-start, flex-end,
baseline. |
flex-wrap |
nowrap (défaut) garde tout sur une ligne quitte à comprimer ;
wrap laisse passer à la ligne — la base des grilles de cartes qui
s'adaptent à l'écran. |
gap |
Un espace entre les enfants seulement (ni avant le premier, ni après le dernier). Avant elle, on posait des marges qu'il fallait annuler sur le dernier. |
| Sur un ENFANT | Ce qu'elle décide |
|---|---|
flex-grow |
« Si le conteneur a de l'espace en trop, quelle part j'en prends ? » —
0 (défaut) : rien. Les valeurs sont des proportions : un enfant à
2 prend deux fois plus qu'un enfant à 1. |
flex-shrink |
« Si l'espace manque, j'accepte de rétrécir ? » — 1 (défaut
web) : oui ; 0 : non, et ça déborde. |
flex-basis |
« Ma taille de départ, avant que grow et shrink n'ajustent. » Défaut
auto = ma taille de contenu, ou mon width si j'en ai un. |
flex: 1 |
Le raccourci roi : grow: 1; shrink: 1; basis: 0. Comme la base est
zéro, tout l'espace devient « libre à répartir » — d'où des enfants tous à
flex: 1 exactement de la même taille, quel que soit leur contenu. C'est
le flex-1 de NativeWind. |
align-self |
L'exception individuelle : cet enfant-là déroge à l'align-items du
conteneur, sans toucher à ses frères. |
justify-content change. space-between colle le premier et le
dernier aux bords — c'est le patron des barres d'en-tête. space-around donne
à chaque enfant la même marge de part et d'autre, si bien que l'espace des extrémités
paraît deux fois plus petit que celui du milieu. space-evenly égalise
vraiment tous les intervalles. Ces trois-là sont indistinguables en mots. Côte à côte,
plus jamais.
Le moyen mnémotechnique qui sert depuis quinze ans : justify = l'axe principal, align =
l'axe croisé. En row, justify gère l'horizontal et align le vertical ; en
column, c'est l'inverse. Tu connais déjà cette gymnastique par NativeWind, elle
est identique.
align-items semble souvent « ne rien faire ». stretch, le
défaut, étire tout le monde à la hauteur du conteneur et efface les différences ; les
trois autres laissent chaque enfant à sa taille et le placent en haut, au milieu ou en
bas. La cinquième valeur, baseline, aligne la ligne d'écriture des
textes plutôt que le bord des boîtes : utile quand les polices diffèrent, rarement
décisive ailleurs.
Trois choses méritent en revanche plus qu'une ligne de tableau, parce que ce sont elles qui te
feront trébucher. La première : les défauts opposés. Le défaut web de
flex-direction est row, celui de React Native est
column — même moteur, choix inverses. Le web a choisi row parce que
flexbox y a d'abord servi à faire des barres de navigation ; RN a choisi column
parce qu'un écran de téléphone est une colonne. Conséquence concrète : le réflexe d'écrire
flex-row pour mettre en ligne, oublie-le en CSS vanilla —
display: flex tout seul met déjà en ligne. C'est la colonne qu'il faut
demander.
La deuxième : flex-shrink. Sur le web il vaut 1, donc
un enfant accepte de rétrécir quand la place manque ; en React Native il vaut 0,
donc l'enfant déborde plutôt que de céder. Même code, deux rendus — et c'est presque toujours
la cause quand un layout se comporte « bizarrement » en passant d'une plateforme à l'autre.
La troisième, min-width: 0, a droit à son aparté un peu plus bas :
c'est le plancher caché qui empêche flex-shrink de faire son travail.
Le symptôme est toujours le même : tu portes un écran de RN vers le web, et tout se
met en ligne alors que tu attendais une colonne — ou l'inverse, un enfant déborde là où
il rétrécissait sagement. Ne relis pas ton HTML : ce sont les deux défauts qu'on
vient de voir, et il n'y en a jamais un troisième.
Vérifie flex-direction, puis flex-shrink,
dans cet ordre. Le réflexe à installer : à chaque display: flex écrit
ou lu, une demi-seconde pour te demander « axe principal : lequel ? ».
Un mot d'expérience vécue, tant qu'on est sur flex: 1 : tu as déjà rencontré côté
mobile le gotcha du flex-1 qui « s'effondre » — un enfant mesuré à zéro dans un
parent sans hauteur contrainte. Le principe vaut des deux côtés : flex: 1
distribue l'espace disponible ; si le parent n'a pas de taille définie sur cet axe, il
n'y a rien à distribuer. Quand un flex: 1 ne marche pas, remonte d'un
cran : c'est presque toujours le parent.
Visualisons avec l'app fil rouge. La page d'accueil de RendezVous montre trois cartes de services — « Consultation », « Vaccination », « Prise de sang » — alignées en une rangée régulière, chacune de même hauteur, avec un espace constant entre elles. En CSS vanilla, tout tient en une règle sur le conteneur :
.services {
display: flex; /* les enfants directs passent en mode flex */
/* pas de flex-direction : le défaut web est ROW → déjà en rangée */
gap: 16px; /* 16px entre chaque carte, ni avant ni après */
align-items: stretch; /* toutes les cartes prennent la hauteur de la */
} /* plus grande — c'est le défaut, écrit pour lire */
.services .carte {
padding: 16px;
border: 1px solid #e2e8f0;
border-radius: 8px;
}
Une rangée de cartes : quatre lignes sur le conteneur. Remarque ce qu'on n'a PAS écrit :
aucun flex-direction (row est le défaut) et aucune marge sur les cartes
(gap s'en charge).
C'est le même algorithme (RN embarque Yoga, une réimplémentation de la spec flexbox), mais les valeurs par défaut diffèrent — et ces différences sont exactement les bugs classiques du dev mobile qui arrive sur le web. La table à mémoriser :
| Propriété | Défaut web | Défaut React Native |
|---|---|---|
| Mode de layout | flux normal — display: flex requis | flexbox partout, d'office |
flex-direction | row | column |
align-items | stretch | stretch (pareil) |
flex-shrink | 1 (les enfants se compriment) | 0 (ils ne se compriment pas) |
box-sizing | content-box (reset requis) | comportement border-box d'office |
Les deux lignes en gras et la ligne flex-shrink sont celles qui coûtent du
temps : quand un layout se comporte « bizarrement » d'un côté ou de l'autre, c'est presque
toujours l'une de ces cinq lignes.
Tu croiseras dans du CSS réel la déclaration mystérieuse min-width: 0 sur un
enfant flex. Par défaut, un enfant flex a min-width: auto, c'est-à-dire « je
refuse d'être plus étroit que mon contenu ». Un texte long d'un seul tenant (une URL, un nom
de fichier) le rend alors incompressible : flex-shrink a beau vouloir
le rétrécir, le contenu fait blocage et toute la rangée déborde. min-width: 0
lève ce plancher — le texte peut être coupé ou recevoir des points de suspension. En
lecture : min-width: 0 sur un item flex = « ici, quelqu'un a combattu un
débordement de texte ».
La barre de réservation de RendezVous : une date, le nom du service (qui peut être long), et un bouton « Réserver ». Voici son CSS, puis les questions.
.barre {
display: flex;
align-items: center;
gap: 12px;
}
.barre .date { flex-shrink: 0; } /* ex. « mardi 12 mars » */
.barre .service { flex: 1; } /* ex. « Consultation de suivi… » */
.barre .bouton { flex-shrink: 0; }
Questions : (1) Dans quel sens les trois éléments sont-ils rangés, et
pourquoi, alors qu'aucun flex-direction n'est écrit ? (2) Quel élément
absorbe l'espace libre quand la barre est large ? (3) Le nom du service est très long
et la barre déborde de l'écran malgré le flex: 1 — pourquoi, et quelle
déclaration de l'aparté ci-dessus manque-t-il ?
Voir le corrigé
(1) Horizontalement, de gauche à droite : le défaut web de
flex-direction est row. Un dev RN s'attendrait à une colonne —
c'est exactement le piège des défauts opposés.
(2) .service, grâce à son flex: 1
(grow: 1) : la date et le bouton gardent leur taille naturelle
(grow à 0 par défaut), et tout l'espace restant va au nom du service.
(3) À cause du plancher min-width: auto : l'enfant flex
refuse d'être plus étroit que son contenu, donc un nom d'un seul tenant très long rend
.service incompressible et pousse la rangée hors de l'écran. Il manque
min-width: 0 sur .service (généralement accompagné d'un
overflow: hidden; text-overflow: ellipsis; white-space: nowrap pour couper
proprement avec des points de suspension).
On veut que le pied de page soit poussé en bas de la fenêtre même quand la page est courte. Le flex: 1 est pourtant bien là, et il ne pousse rien du tout. Pourquoi ?
.page {
display: flex;
flex-direction: column;
}
.page__contenu { flex: 1; } /* censé absorber l'espace restant */
.page__pied { padding: 16px; }
Parce que flex: 1 distribue l'espace disponible, et qu'ici il n'y en a aucun. .page n'a reçu aucune hauteur, et sur le web un bloc épouse par défaut la hauteur de son contenu : la taille du parent sur l'axe vertical est donc exactement la somme de ses enfants. Espace restant à répartir : zéro. Chacun garde sa taille naturelle, et le pied se colle sous le contenu au lieu de descendre en bas de l'écran.
Le correctif est sur le parent, jamais sur l'enfant : .page { min-height: 100vh; } — 100 % de la hauteur de la fenêtre — crée enfin de l'espace à distribuer. Le réflexe à installer, et il vaut des deux côtés : quand un flex: 1 ne fait rien, remonte d'un cran. C'est le même gotcha que celui du flex-1 qui s'effondre dans un parent sans hauteur contrainte, côté React Native.
Centrer, enfin
« Comment centre-t-on une div ? » est une vraie question d'entrevue, et la réponse tient en deux formules — pas une de plus.
Formule 1 — le centrage flex, pour centrer le contenu d'un conteneur, horizontalement et verticalement à la fois :
.parent {
display: flex;
justify-content: center; /* centre sur l'axe principal (horizontal en row) */
align-items: center; /* centre sur l'axe croisé (vertical en row) */
}
Le centrage parfait en trois déclarations sur le parent. Seule condition : que le parent ait une hauteur — on ne peut pas centrer verticalement dans un conteneur qui épouse son contenu.
Formule 2 — margin: auto, pour centrer un bloc dans la
page. Sur un élément de type bloc dont la largeur est bornée — par un width, ou
par un max-width comme dans l'exemple ci-dessous —, margin-left: auto et
margin-right: auto signifient « répartissez l'espace restant équitablement des
deux côtés » — donc centrage horizontal. C'est le patron immémorial du contenu de page :
.page {
max-width: 720px; /* jamais plus large que ça (confort de lecture) */
margin: 0 auto; /* 0 en vertical, auto = centrage en horizontal */
}
Le duo max-width + margin: 0 auto : la colonne de contenu centrée
qu'on retrouve sur pratiquement tous les sites, celui-ci compris.
Comment choisir ? Question de point de vue : le centrage flex se déclare sur le
parent (« centre tout ce que je contiens ») ; le margin: auto se
déclare sur l'enfant (« centre-moi dans l'espace qu'on me donne ») et ne fait
que l'horizontal. En pratique : contenu de page → formule 2 ; tout le reste (icône dans un
bouton, spinner, texte dans une pastille) → formule 1. Détail utile en entrevue : dans un
conteneur flex, les marges auto absorbent l'espace libre dans toutes les
directions, donc margin: auto sur un enfant flex unique le centre parfaitement.
La rangée flexible
.row {
display: flex; /* passe les enfants en flexbox */
align-items: center; /* alignés verticalement au centre */
justify-content: space-between; /* premiers/derniers aux bords, */
gap: 12px; /* espace régulier entre les enfants */
}
C'est le patron le plus tapé du frontend web — barres d'en-tête, lignes de liste, pieds de
carte : un coup d'œil et tu le reconnais partout. En React Native il s'écrit exactement
pareil, à un détail près : il faut ajouter flexDirection: 'row', puisque le
défaut y est column.
RendezVous l'utilise mot pour mot pour chaque ligne de la page « Mes rendez-vous » :
à gauche la date et le service, à droite le badge d'état et le bouton Annuler.
display: flex met tout sur une ligne, align-items: center aligne
texte, badge et bouton malgré leurs hauteurs différentes,
justify-content: space-between pousse les informations à gauche et les actions à
droite, gap évite que le badge colle au bouton. Ouvre les DevTools sur n'importe
quel site de prise de rendez-vous : tu y retrouveras cette règle, au pixel près.
Le texte de la pastille est parfaitement centré verticalement… et reste collé à gauche. Quelle déclaration manque, et pourquoi celle qui est écrite ne fait-elle pas ce qu'on attendait d'elle ?
.pastille {
display: flex;
flex-direction: column;
justify-content: center;
width: 200px;
height: 200px;
}
Il manque align-items: center;. Le piège est l'inversion des axes : justify-content agit toujours sur l'axe principal, et flex-direction: column a fait de la verticale l'axe principal — d'où le centrage vertical, effectivement obtenu. L'horizontale est devenue l'axe croisé, et c'est align-items qui la gouverne. En row, les deux rôles seraient exactement échangés.
C'est pour ça que le moyen mnémotechnique ne parle jamais d'horizontal ni de vertical : justify = axe principal, align = axe croisé ; c'est flex-direction qui dit lequel est lequel. Retiens aussi la condition tacite du centrage vertical, remplie ici par le height: 200px : sans hauteur, le conteneur épouse son contenu et il n'y a rien à centrer.
Lire le CSS de quelqu'un d'autre
Dernière compétence du module, et la plus rentable pour un test technique ou un premier jour de stage : ouvrir une feuille de style écrite par quelqu'un d'autre et s'y orienter vite. Un fichier réel fait des centaines de lignes ; on ne le lit pas de haut en bas. Stratégie en quatre temps.
- Repère le reset et les fondations, tout en haut : le
box-sizing: border-boxet les règles surbody(police, couleurs). Elles donnent l'ambiance et les valeurs héritées partout — car certaines propriétés (color,font-family,line-height) s'héritent : posées surbody, elles ruissellent sur tous les descendants qui ne les redéfinissent pas. D'où une seule règle qui typographie toute la page. - Identifie les classes « racines » qui structurent la page —
.layout,.sidebar,.content… généralement les premiers grands blocs après le reset, souvent des conteneurs flex. Elles donnent le squelette ; le reste est du remplissage. - Suis une classe à la trace : Ctrl+F sur
.carteet lis toutes ses occurrences — la règle de base, mais aussi.carte:hoverou un.services .cartequi la surcharge. Le style final d'un élément est la somme arbitrée de toutes les règles qui le visent, jamais une seule. - Vérifie dans le navigateur, pas dans ta tête : clic droit → « Inspecter » → panneau Styles des DevTools. Il liste toutes les règles qui visent l'élément, de la plus forte à la plus faible, et barre les déclarations perdantes — tu vois l'arbitrage. L'onglet « Computed » dessine même le modèle de boîte avec ses valeurs réelles. Bonus : tu peux modifier les valeurs en direct (un rechargement efface tout) — quand tu te demandes « à quoi sert cette règle ? », désactive-la et regarde ce qui bouge. C'est le meilleur outil d'apprentissage du CSS qui existe.
La carte de service de RendezVous, en CSS vanilla. Le viewport (l'écran) fait 360 px de large.
.carte {
width: 320px;
padding: 16px;
border: 2px solid #e2e8f0;
margin: 0 12px;
}
Questions : (1) Sans le reset border-box
(donc en content-box, le défaut), quelle est la largeur visible de la carte
(bordure comprise), et combien d'espace horizontal total occupe-t-elle avec ses
marges ? Rentre-t-elle dans l'écran ? (2) Avec
box-sizing: border-box, mêmes questions. (3) Dans les deux cas, la marge
fait-elle partie de la « largeur » de la carte ?
Voir le corrigé
(1) En content-box, width: 320px ne mesure que
le contenu. Largeur visible = 320 + 16 × 2 (padding) +
2 × 2 (bordure) = 356 px. Avec les marges :
356 + 12 × 2 = 380 px d'espace occupé — ça ne rentre
PAS dans les 360 px de l'écran : débordement horizontal, la page « scrolle » de
côté. Le grand classique.
(2) En border-box, width: 320px = le rectangle
visible, padding et bordure compris : largeur visible =
320 px tout rond (le contenu se comprime à
320 − 32 − 4 = 284 px). Espace total avec marges :
320 + 24 = 344 px — ça rentre. Voilà, en un exemple chiffré,
pourquoi tout le monde met border-box.
(3) Non, jamais : la margin est en dehors de la boîte dans les
deux modes. box-sizing ne décide que du sort du padding et de la bordure ;
la marge est toujours de l'espace entre les boîtes, en plus.
« Peux-tu m'expliquer le modèle de boîte ? »
« Chaque élément est un rectangle à quatre couches : le contenu au centre, puis le padding — la marge intérieure, celle qui porte le fond —, puis la bordure, et enfin la margin, qui est extérieure et transparente. »
« Mais le vrai point, c'est box-sizing. Par défaut, en
content-box, width ne mesure que le contenu : le padding et la
bordure viennent s'ajouter par-dessus. Donc un bloc à width: 300px avec
20 px de padding occupe 340 px, ce qui casse toutes les mises en page qu'on
croyait calculées. C'est pour ça qu'on met box-sizing: border-box partout en
première ligne du projet : width mesure alors le rectangle visible, bordure
comprise, et le chiffre qu'on écrit est celui qu'on obtient. »
« Comment centres-tu un élément horizontalement et verticalement ? »
« Avec flexbox, sur le parent : display: flex,
justify-content: center pour l'axe principal,
align-items: center pour l'axe croisé. Avec une condition qu'on oublie
souvent — le parent doit avoir une hauteur, sinon il fait exactement la taille de son
contenu et il n'y a rien à centrer verticalement. »
« Pour le cas particulier "centrer un bloc de contenu dans la page", je ne sors même pas
flexbox : max-width plus margin: 0 auto sur l'élément lui-même
suffit. Et display: grid; place-items: center fait le travail en une
déclaration — mais flexbox reste ce qu'on attend d'entendre. »
« Quelle est la différence entre margin et padding ? »
« Le padding est à l'intérieur de la boîte, entre le contenu et la bordure : il porte la couleur de fond et il fait partie de la surface cliquable — c'est pour ça qu'on agrandit un bouton avec du padding et jamais avec de la margin. La margin, elle, est à l'extérieur : de l'espace transparent entre la boîte et ses voisines. »
« En pratique je tranche avec une seule question : est-ce que je veux de l'air
dans le composant, ou autour ? Dedans, c'est padding. Autour, c'est
margin — ou mieux, un gap sur le parent flex, qui espace les enfants sans
qu'aucun n'ait à porter de marge, et qui évite d'avoir à retirer la marge du dernier
élément. »
La cascade arbitre les règles en conflit : origine, puis
spécificité (inline > id > classe > élément), puis ordre
d'écriture. Chaque élément est une boîte — contenu, padding, bordure,
margin — et box-sizing: border-box fait que width mesure le
rectangle visible, comme React Native le fait d'office. Le web pose les éléments dans le
flux normal tant qu'un conteneur ne déclare pas display: flex.
Flexbox : le conteneur choisit l'axe (défaut web = row, RN = column),
distribue (justify-content), aligne (align-items), espace
(gap) ; les enfants négocient avec flex-grow/shrink/basis, dont le
raccourci roi flex: 1. Centrer : flex sur le parent, ou max-width +
margin: 0 auto pour un bloc de page. Devant le CSS d'autrui : reset, classes
racines, Ctrl+F, puis DevTools > Styles pour voir la cascade arbitrer en vrai.
Et ailleurs : Ce module est du muscle pur pour les tests techniques :
le modèle de boîte, la spécificité et « centrer une div » sont des questions posées telles
quelles, et un « intègre cette maquette en CSS vanilla » se résout à 80 % avec la rangée
flexible, gap et max-width + margin: 0 auto. Le
transfert joue dans les deux sens : chaque classe Tailwind ou NativeWind est un alias d'une
déclaration vue ici (items-center = align-items: center,
flex-1 = flex: 1). Comprendre le CSS d'en dessous, c'est déboguer
Tailwind sans tâtonner. Suite au module « CSS 2 — grid, responsive, variables ».
flex-direction- vaut
rowsur le web,columnen React Native flex-shrink- vaut
1sur le web (l'enfant cède),0en RN (il déborde) justify-content- agit sur l'axe principal ;
align-itemssur l'axe croisé box-sizing- défaut
content-box:widthexclut padding et bordure - Marges verticales
- fusionnent entre blocs voisins — on garde la plus grande, pas la somme. Jamais dans un conteneur flex
- Spécificité
id(100) > classe (10) > balise (1) ; à égalité, le dernier déclaré gagne- Centrer un bloc
max-width+margin: 0 auto(horizontal seulement)- Centrer un contenu
display: flex+justify-content+align-items— exige une hauteur pour le vertical