StyleSheet et Flexbox
Le système de styles de React Native ressemble au CSS, mais avec des différences fondamentales. Il n'y a pas de fichiers CSS, pas de cascade, pas de sélecteurs. Les styles sont des objets JavaScript appliqués directement aux composants.
Pas de CSS en React Native
En React web, vous utilisez des fichiers CSS (ou className avec Tailwind). En React Native, les styles sont définis en JavaScript avec StyleSheet.create() :
// React web : fichier CSS séparé ou className
<div className="container">
<h1 className="title">Bonjour</h1>
</div>
// React Native : styles en JavaScript
<View style={styles.container}>
<Text style={styles.title}>Bonjour</Text>
</View>
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
},
});
Pourquoi pas de CSS ?
React Native ne s'exécute pas dans un navigateur. Il n'y a pas de moteur CSS. Les styles sont traduits en propriétés natives de chaque plateforme (iOS et Android). StyleSheet.create() convertit vos objets JavaScript en instructions compréhensibles par les composants natifs.
Pas de cascade
En CSS web, les styles se propagent par héritage (cascade) : si un <div> a color: red, tous ses enfants texte héritent de cette couleur. En React Native, il n'y a pas de cascade. Chaque composant doit recevoir ses propres styles explicitement. La seule exception : les styles de texte se propagent entre composants Text imbriqués.
// ❌ Ne fonctionne pas : la couleur ne se propage pas de View vers Text
<View style={{ color: 'red' }}>
<Text>Ce texte n'est PAS rouge</Text>
</View>
// ✅ Chaque Text doit avoir son propre style
<View>
<Text style={{ color: 'red' }}>Ce texte est rouge</Text>
</View>
// ✅ Exception : les Text imbriqués héritent des styles du Text parent
<Text style={{ color: 'red' }}>
Texte rouge <Text style={{ fontWeight: 'bold' }}>et celui-ci aussi (rouge + gras)</Text>
</Text>
Pourquoi React Native n'utilise-t-il pas de fichiers CSS ?
StyleSheet.create()
StyleSheet.create() prend un objet dont chaque propriété est un objet de style. Il retourne un objet optimisé pour les performances.
import { View, Text, StyleSheet } from 'react-native';
function Card({ title, description }) {
return (
<View style={styles.card}>
<Text style={styles.cardTitle}>{title}</Text>
<Text style={styles.cardDescription}>{description}</Text>
</View>
);
}
// Définition des styles en bas du fichier
const styles = StyleSheet.create({
card: {
backgroundColor: '#ffffff',
borderRadius: 8,
padding: 16,
marginBottom: 12,
// Ombre sur iOS
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
// Ombre sur Android
elevation: 3,
},
cardTitle: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 4,
},
cardDescription: {
fontSize: 14,
color: '#666',
lineHeight: 20,
},
});
Convention
Les styles sont définis en bas du fichier, après le composant. C'est une convention largement suivie dans la communauté React Native. On les nomme styles (au pluriel).
Propriétés en camelCase
Les propriétés CSS sont écrites en camelCase (comme le style inline en React web) :
| CSS web | React Native |
|---|---|
background-color | backgroundColor |
font-size | fontSize |
font-weight | fontWeight |
border-radius | borderRadius |
margin-bottom | marginBottom |
text-align | textAlign |
Où définit-on habituellement les styles dans un fichier React Native ?
Unités : pas de pixels CSS
En React Native, toutes les dimensions sont en density-independent pixels (dp). Il n'y a pas d'unités comme px, rem, em, % (sauf pour Flexbox).
const styles = StyleSheet.create({
container: {
padding: 16, // 16 dp, pas 16px
marginBottom: 12, // 12 dp
},
title: {
fontSize: 24, // 24 dp
},
});
Qu'est-ce qu'un dp (density-independent pixel) ?
Un dp s'affiche à la même taille physique sur tous les appareils, quelle que soit la densité de pixels de l'écran :
- Sur un écran 1x (ancien) : 1 dp = 1 pixel physique
- Sur un écran 2x (Retina) : 1 dp = 2 pixels physiques
- Sur un écran 3x (iPhone Pro) : 1 dp = 3 pixels physiques
Résultat : fontSize: 16 donne un texte de la même taille sur tous les téléphones, que l'écran fasse 720p ou 4K.
Pas d'unité à écrire
En React Native, vous écrivez juste le nombre, sans unité :
// ❌ Pas d'unité en React Native
padding: '16px' // Erreur
fontSize: '1.5rem' // Erreur
// ✅ Juste le nombre (en dp)
padding: 16
fontSize: 24
Flexbox : le moteur de mise en page
React Native utilise Flexbox pour la mise en page, comme en CSS web. Mais avec une différence importante : la direction par défaut est column (vertical), pas row (horizontal).
Flexbox en React Native vs CSS web
| Propriété | CSS web (défaut) | React Native (défaut) |
|---|---|---|
flexDirection | row (horizontal) | column (vertical) |
display | block par défaut | flex toujours |
position | static | relative |
flexDirection: column par défaut
C'est le piège classique quand on vient du web. En CSS, flexDirection est row par défaut (les éléments s'alignent horizontalement). En React Native, c'est column (les éléments s'empilent verticalement). Si vous voulez des éléments côte à côte, ajoutez explicitement flexDirection: 'row'.
Exemples de mise en page
Empiler verticalement (défaut)
// Les enfants s'empilent verticalement (flexDirection: 'column' par défaut)
<View style={styles.container}>
<Text>Premier</Text>
<Text>Deuxième</Text>
<Text>Troisième</Text>
</View>
// Résultat :
// Premier
// Deuxième
// Troisième
Aligner horizontalement
// flexDirection: 'row' pour aligner horizontalement
<View style={styles.row}>
<Text>A</Text>
<Text>B</Text>
<Text>C</Text>
</View>
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
gap: 12,
},
});
// Résultat :
// A B C
Centrer un contenu
// Centrer horizontalement et verticalement
<View style={styles.centered}>
<Text>Je suis centré !</Text>
</View>
const styles = StyleSheet.create({
centered: {
flex: 1,
justifyContent: 'center', // Centre sur l'axe principal (vertical par défaut)
alignItems: 'center', // Centre sur l'axe secondaire (horizontal par défaut)
},
});
Quelle est la direction par défaut de Flexbox en React Native ?
La propriété flex
La propriété flex définit comment un élément occupe l'espace disponible :
<View style={{ flex: 1 }}>
{/* Prend tout l'espace restant */}
<View style={{ flex: 2, backgroundColor: '#3498db' }}>
<Text style={{ color: '#fff' }}>Zone 1 (2/3 de l'espace)</Text>
</View>
<View style={{ flex: 1, backgroundColor: '#e74c3c' }}>
<Text style={{ color: '#fff' }}>Zone 2 (1/3 de l'espace)</Text>
</View>
</View>
flex: 1sur le conteneur = prend tout l'espace disponible- Les enfants avec
flex: 2etflex: 1se partagent l'espace dans un ratio 2:1
gap : espacement entre éléments
Depuis React Native 0.71, la propriété gap fonctionne comme en CSS :
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
gap: 12, // Espace entre chaque enfant (12 dp)
},
grid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 8, // Espace entre les éléments de la grille
},
});
Combiner des styles
Vous pouvez combiner plusieurs styles avec un tableau :
function Badge({ label, type }) {
return (
<View style={[styles.badge, type === 'success' && styles.badgeSuccess]}>
<Text style={[styles.badgeText, type === 'success' && styles.badgeTextSuccess]}>
{label}
</Text>
</View>
);
}
const styles = StyleSheet.create({
badge: {
paddingHorizontal: 12,
paddingVertical: 4,
borderRadius: 12,
backgroundColor: '#e0e0e0',
},
badgeSuccess: {
backgroundColor: '#4caf50',
},
badgeText: {
fontSize: 12,
color: '#333',
},
badgeTextSuccess: {
color: '#fff',
},
});
Le dernier style du tableau a la priorité (comme les classes CSS qui se superposent). Vous pouvez aussi combiner styles définis et styles inline :
// Style défini + style inline conditionnel
<View style={[styles.card, { opacity: isDisabled ? 0.5 : 1 }]}>
<Text>Contenu</Text>
</View>
Comment appliquer conditionnellement un style en React Native ?
Patterns courants
Carte avec ombre
const styles = StyleSheet.create({
card: {
backgroundColor: '#fff',
borderRadius: 12,
padding: 16,
margin: 8,
// Ombre iOS
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
// Ombre Android
elevation: 4,
},
});
Ombres différentes sur iOS et Android
Les ombres se gèrent différemment selon la plateforme :
- iOS : utilise
shadowColor,shadowOffset,shadowOpacity,shadowRadius - Android : utilise uniquement
elevation(nombre entier)
Pour un comportement cohérent, définissez les deux dans vos styles. Chaque plateforme ignorera les propriétés qui ne la concernent pas.
Ligne séparatrice
const styles = StyleSheet.create({
separator: {
height: 1,
backgroundColor: '#e0e0e0',
marginVertical: 12,
},
});
En-tête d'écran
const styles = StyleSheet.create({
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 12,
backgroundColor: '#fff',
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
});
Contenu centré (plein écran)
const styles = StyleSheet.create({
centered: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f5f5f5',
},
});
Exemple complet : Écran de paramètres
import { View, Text, StyleSheet, SafeAreaView, ScrollView } from 'react-native';
function SettingsItem({ label, value }) {
return (
<View style={styles.settingsItem}>
<Text style={styles.settingsLabel}>{label}</Text>
<Text style={styles.settingsValue}>{value}</Text>
</View>
);
}
function SectionTitle({ title }) {
return <Text style={styles.sectionTitle}>{title}</Text>;
}
export default function SettingsScreen() {
return (
<SafeAreaView style={styles.safeArea}>
<ScrollView style={styles.container}>
<Text style={styles.pageTitle}>Paramètres</Text>
<SectionTitle title="Compte" />
<View style={styles.section}>
<SettingsItem label="Nom" value="Alice Dupont" />
<View style={styles.separator} />
<SettingsItem label="Email" value="alice@example.com" />
<View style={styles.separator} />
<SettingsItem label="Langue" value="Français" />
</View>
<SectionTitle title="Notifications" />
<View style={styles.section}>
<SettingsItem label="Push" value="Activé" />
<View style={styles.separator} />
<SettingsItem label="Email" value="Désactivé" />
</View>
<SectionTitle title="Application" />
<View style={styles.section}>
<SettingsItem label="Version" value="1.0.0" />
<View style={styles.separator} />
<SettingsItem label="SDK Expo" value="52" />
</View>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: '#f2f2f7',
},
container: {
flex: 1,
padding: 16,
},
pageTitle: {
fontSize: 34,
fontWeight: 'bold',
marginBottom: 24,
},
sectionTitle: {
fontSize: 13,
fontWeight: '600',
color: '#666',
textTransform: 'uppercase',
marginBottom: 8,
marginTop: 16,
marginLeft: 16,
},
section: {
backgroundColor: '#fff',
borderRadius: 12,
paddingHorizontal: 16,
// Ombre iOS
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.05,
shadowRadius: 2,
// Ombre Android
elevation: 1,
},
settingsItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 12,
},
settingsLabel: {
fontSize: 17,
},
settingsValue: {
fontSize: 17,
color: '#999',
},
separator: {
height: 1,
backgroundColor: '#e0e0e0',
marginLeft: 0,
},
});
Quelle propriété utiliser pour ajouter une ombre sur Android ?
À retenir
Comprendre, pas mémoriser
StyleSheet :
- Pas de fichiers CSS : les styles sont des objets JavaScript dans
StyleSheet.create() - Pas de cascade : chaque composant doit recevoir ses propres styles
- Propriétés en camelCase :
backgroundColor,fontSize,borderRadius - Dimensions en dp (density-independent pixels) : pas de
px,rem,%
Flexbox :
- Direction par défaut = column (vertical), pas row comme en CSS web
- Tous les View sont des flex containers par défaut
flex: 1= prend tout l'espace disponiblejustifyContent+alignItemspour centrer le contenugappour l'espacement entre éléments
Patterns :
- Combiner des styles avec un tableau :
style={[styles.base, styles.variant]} - Ombres :
shadowColor/shadowOffsetpour iOS,elevationpour Android