Chapitre 4.2 — Flexbox à la sauce React Native
Où on en est : vous savez écrire un style. Reste à disposer les éléments — et RN ne connaît qu’un seul système de layout : flexbox. Le vôtre, à cinq différences près.
⏱️ TL;DR — TOUT est flexbox en RN (chaque View est un conteneur flex). Les 5 différences avec le web : (1)
flexDirection: 'column'par défaut, (2)alignItems: 'stretch'par défaut, (3)flexest un nombre (flex: 1), (4) pas dedisplay: block/grid— flex ou rien, (5) tout estbox-sizing: border-box.gapfonctionne. Les layouts types d’une app (header/ contenu/footer, lignes espacées, grilles wrap) tiennent en trois patterns.
🎯 Objectifs
- Intégrer les 5 différences web → RN une bonne fois.
- Maîtriser
flex: 1(et pourquoi un écran « vide » vient d’un flex manquant). - Écrire les 3 layouts types : colonne d’écran, row d’items, grille wrap.
- Poser le layout complet de l’écran de quiz de Drill.
Différence n°1 : column par défaut
Le web empile en row (l’écriture latine va à droite) ; le mobile empile en column (un
téléphone est vertical). C’est LA différence qui déroute :
// Ces trois Text s'empilent VERTICALEMENT (pas besoin de flexDirection)
<View>
<Text>Question 1</Text>
<Text>Question 2</Text>
<Text>Question 3</Text>
</View>
// Pour une ligne : demander explicitement row
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
<Text>⏱️</Text>
<Text>12 s restantes</Text>
</View>Conséquence mentale : en RN, main axis = vertical par défaut. justifyContent distribue
donc verticalement, alignItems aligne horizontalement — l’inverse de vos réflexes web tant
que vous n’avez pas explicitement mis row.
Différence n°2 : stretch par défaut
alignItems vaut stretch : les enfants s’étirent sur l’axe croisé. Dans une colonne, vos
enfants prennent toute la largeur (sur le web, défaut stretch aussi — mais les div block
masquaient le sujet). D’où : un bouton qu’on veut « à sa taille » demande
alignSelf: 'flex-start' (ou un alignItems différent sur le parent).
Différence n°3 : flex est un nombre
Pas de flex: 1 1 0% — un nombre :
flex: 1: prends tout l’espace disponible (et partage-le à parts égales entre frères qui ont aussiflex).flex: 2vsflex: 1: deux fois plus d’espace.- Sans
flex: l’élément mesure son contenu.
Le bug le plus fréquent du layout RN en découle :
// ❌ Écran « vide » : la FlatList mesure 0 (son parent ne lui donne pas d'espace)
<View>
<FlatList … />
</View>
// ✅ Le parent remplit l'écran, la liste remplit le parent
<View style={{ flex: 1 }}>
<FlatList … />
</View>⚠️ Piège — « Ma liste/mon image/mon contenu ne s’affiche pas » = dans 80 % des cas, un
flex: 1manquant quelque part dans la chaîne des parents. La hauteur ne « remonte » pas du contenu pour les composants qui remplissent : elle descend du parent. Réflexe de debug : l’element inspector (chapitre 2.4), et vérifier chaque maillon depuis la racine.
Différences n°4 et 5 : rien d’autre que flex, et border-box
Pas de display: block, inline, grid — chaque View est un conteneur flex, point. Les
grilles se font en flexWrap (ci-dessous) ou avec numColumns de FlatList (chapitre 3.6).
Et toutes les boîtes sont en border-box (padding et bordure inclus dans width/height) — le
comportement que vous forciez de toute façon sur le web.
Les trois layouts types
1. La colonne d’écran (header fixe / contenu extensible / footer fixe) — l’écran de quiz de Drill :
// app/quiz.tsx — squelette de layout
<View style={styles.screen}>
<View style={styles.header}>{/* timer + score : hauteur = contenu */}</View>
<View style={styles.body}>{/* question : prend TOUT le reste */}</View>
<View style={styles.footer}>{/* 4 AnswerButton : hauteur = contenu */}</View>
</View>
const styles = StyleSheet.create({
screen: { flex: 1, padding: 16, gap: 16 },
header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
body: { flex: 1, justifyContent: 'center' }, // ← la question se centre dans l'espace restant
footer: { gap: 10 }, // ← les boutons s'empilent, espacés
})Le pattern : les zones fixes mesurent leur contenu, une seule zone a flex: 1 et absorbe
le reste. C’est le squelette de 90 % des écrans mobiles.
2. La row d’item (déjà vue dans ThemeCard) : flexDirection: 'row' +
alignItems: 'center' + un enfant flex: 1 qui pousse le reste aux bords :
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
<Text style={{ fontSize: 32 }}>🐘</Text>
<View style={{ flex: 1 }}>
<Text numberOfLines={1}>PHP 8.5</Text>
</View>
<Text>142 Q</Text> {/* collé à droite grâce au flex: 1 du milieu */}
</View>3. La grille wrap (les badges de difficulté, des chips de filtres) :
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8 }}>
{tags.map(t => <Chip key={t} label={t} />)}
</View>gap (et rowGap/columnGap) fonctionne comme sur le web — adieu les marges négatives des
vieux tutos.
💡 Pour un dev Next.js — Ce que vous perdez vraiment :
position: sticky(n’existe pas — les headers collants viennent de la navigation, Partie 5) etgrid(flexWrap + numColumns couvrent les cas mobiles réels).position: 'absolute'existe, relatif au parent — utile pour les badges superposés et les overlays. Tout le reste de vos réflexes flexbox est valide tel quel :justifyContent,alignItems,alignSelf,flexGrow/Shrink/Basisexistent aussi si vous en avez besoin.
✏️ Exercices
1. Sans coder, prédisez : une View racine avec trois enfants <Text> sans aucun style —
comment s’affichent-ils ? Ajoutez flexDirection: 'row' sur la racine — que devient
l’affichage ? Puis vérifiez sur le téléphone.
✅ Solution
Sans style : trois lignes empilées verticalement (column par défaut), chacune large comme
le parent (stretch). En row : les trois textes se placent côte à côte à gauche, chacun
large comme son contenu, et s’étirent en hauteur sur la ligne (stretch sur l’axe croisé devenu
vertical).
2. Construisez l’écran de résultat de Drill : un grand score centré au milieu de l’écran, un bouton « Rejouer » plein largeur collé en bas (au-dessus de la safe area).
✅ Solution
const insets = useSafeAreaInsets()
<View style={{ flex: 1, padding: 16, paddingBottom: insets.bottom + 16 }}>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text style={styles.bigScore}>870 pts</Text>
</View>
<AnswerButton label="Rejouer" onPress={replay} />
</View>La zone centrale a flex: 1 (absorbe tout) et centre son contenu dans les deux axes ; le
bouton, hors de cette zone, reste en bas naturellement — pas d’absolu nécessaire.
3. Dans la row d’item type, retirez le flex: 1 du bloc central. Qu’arrive-t-il au compteur
de droite et au titre long ? Expliquez.
✅ Solution
Le bloc central mesure alors son contenu : un titre court laisse le compteur flotter juste à
côté (plus collé à droite), et un titre long pousse le compteur hors de l’écran (la row ne
wrappe pas par défaut). flex: 1 sur le milieu fait les deux jobs : occuper l’espace (pousser
à droite) et borner le texte (permettre à numberOfLines de tronquer).
🧠 Quiz
1. Quelles sont les valeurs par défaut de flexDirection et alignItems en RN ?
Réponse
flexDirection: 'column' (le web : row) et alignItems: 'stretch'. Main axis vertical par
défaut — justifyContent distribue verticalement tant qu’on n’a pas mis row.
2. Que fait flex: 1 exactement ?
Réponse
« Prends tout l’espace disponible dans l’axe principal du parent » — partagé au prorata entre frères ayant un flex (flex: 2 = double part). Sans flex, l’élément mesure son contenu.
3. Symptôme : FlatList invisible, écran blanc. Cause probable n°1 ?
Réponse
Un flex: 1 manquant dans la chaîne des parents : la liste reçoit une hauteur de 0. La hauteur descend du parent, elle ne remonte pas du contenu.
4. Comment faire une grille de chips qui passe à la ligne ?
Réponse
flexDirection: 'row' + flexWrap: 'wrap' + gap sur le conteneur.
5. position: sticky en RN ?
Réponse
N’existe pas. Les headers collants viennent du système de navigation (Partie 5) ou de
stickyHeaderIndices sur ScrollView/FlatList pour les cas simples.
👉 Chapitre suivant : 4.3 — Thème & dark mode — la palette de Drill et un vrai système de thème.