Skip to Content

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) flex est un nombre (flex: 1), (4) pas de display: block/grid — flex ou rien, (5) tout est box-sizing: border-box. gap fonctionne. 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 aussi flex).
  • flex: 2 vs flex: 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: 1 manquant 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) et grid (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/Basis existent 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.