Directives UI Expo
Références
Consultez ces ressources selon vos besoins :
references/
animations.md Reanimated : entrée, sortie, layout, scroll-driven, gestures
controls.md iOS natif : Switch, Slider, SegmentedControl, DateTimePicker, Picker
form-sheet.md Form sheets dans expo-router : configuration, footers et interaction du fond.
gradients.md CSS gradients via experimental_backgroundImage (New Arch uniquement)
icons.md SF Symbols via expo-image (sf: source), noms, animations, poids
media.md Camera, audio, video et sauvegarde de fichiers
route-structure.md Conventions de routes, routes dynamiques, groupes, organisation des dossiers
search.md Barre de recherche avec headers, hook useSearch, modèles de filtrage
storage.md SQLite, AsyncStorage, SecureStore
tabs.md NativeTabs, migration depuis les tabs JS, fonctionnalités iOS 26
toolbar-and-headers.md Headers de Stack et boutons toolbar, menus, recherche (iOS uniquement)
visual-effects.md Blur (expo-blur) et liquid glass (expo-glass-effect)
webgpu-three.md Graphiques 3D, jeux, visualisations GPU avec WebGPU et Three.js
zoom-transitions.md Apple Zoom : transitions de zoom fluides avec Link.AppleZoom (iOS 18+)
Exécuter l'application
CRITIQUE : Essayez toujours Expo Go avant de créer des builds personnalisés.
La plupart des applications Expo fonctionnent dans Expo Go sans code natif personnalisé. Avant d'exécuter npx expo run:ios ou npx expo run:android :
- Commencez par Expo Go : Exécutez
npx expo startet scannez le code QR avec Expo Go - Vérifiez que les fonctionnalités fonctionnent : Testez votre application en détail dans Expo Go
- Créez des builds personnalisés uniquement si nécessaire - voir ci-dessous
Quand les builds personnalisés sont nécessaires
Vous avez besoin de npx expo run:ios/android ou eas build UNIQUEMENT lors de l'utilisation de :
- Modules Expo locaux (code natif personnalisé dans
modules/) - Cibles Apple (widgets, app clips, extensions via
@bacons/apple-targets) - Modules natifs tiers non inclus dans Expo Go
- Configuration native personnalisée qui ne peut pas être exprimée dans
app.json
Quand Expo Go fonctionne
Expo Go supporte une large gamme de fonctionnalités directement :
- Tous les packages
expo-*(camera, location, notifications, etc.) - Navigation Expo Router
- La plupart des bibliothèques UI (reanimated, gesture handler, etc.)
- Push notifications, deep links, et bien plus
Si vous êtes incertain, essayez Expo Go en premier. Créer des builds personnalisés ajoute de la complexité, ralentit l'itération et requiert la configuration de Xcode/Android Studio.
Style de code
- Soyez prudent avec les chaînes non terminées. Assurez-vous que les backticks imbriquées sont échappés ; n'oubliez jamais d'échapper correctement les guillemets.
- Utilisez toujours les instructions import en haut du fichier.
- Utilisez toujours kebab-case pour les noms de fichiers, par ex.
comment-card.tsx - Supprimez toujours les anciens fichiers de route lors du déplacement ou de la restructuration de la navigation
- Ne jamais utiliser de caractères spéciaux dans les noms de fichiers
- Configurez tsconfig.json avec des alias de chemin et préférez les alias aux imports relatifs pour les refactorisations.
Routes
Consultez ./references/route-structure.md pour les conventions de routes détaillées.
- Les routes appartiennent au répertoire
app. - Ne jamais co-localiser les composants, types ou utilitaires dans le répertoire app. C'est un anti-pattern.
- Assurez-vous que l'application a toujours une route correspondant à "/", elle peut être à l'intérieur d'une route de groupe.
Préférences des bibliothèques
- Ne jamais utiliser les modules supprimés de React Native tels que Picker, WebView, SafeAreaView ou AsyncStorage
- Ne jamais utiliser l'ancienne expo-permissions
expo-audioet nonexpo-avexpo-videoet nonexpo-avexpo-imageavecsource="sf:name"pour les SF Symbols, pasexpo-symbolsou@expo/vector-iconsreact-native-safe-area-contextet non react-native SafeAreaViewprocess.env.EXPO_OSet nonPlatform.OSReact.useet nonReact.useContext- Composant
expo-imageImage au lieu de l'élément intrinsèqueimg expo-glass-effectpour les fonds liquid glass
Responsivité
- Enveloppez toujours le composant racine dans une scroll view pour la responsivité
- Utilisez
<ScrollView contentInsetAdjustmentBehavior="automatic" />au lieu de<SafeAreaView>pour des insets de zone sûre plus intelligents contentInsetAdjustmentBehavior="automatic"doit aussi être appliqué à FlatList et SectionList- Utilisez flexbox au lieu de l'API Dimensions
- PRÉFÉREZ TOUJOURS
useWindowDimensionsàDimensions.get()pour mesurer la taille de l'écran
Comportement
- Utilisez expo-haptics conditionnellement sur iOS pour des expériences plus agréables
- Utilisez les vues avec haptics intégrés comme
<Switch />de React Native et@react-native-community/datetimepicker - Quand une route appartient à une Stack, son premier enfant devrait presque toujours être une ScrollView avec
contentInsetAdjustmentBehavior="automatic"défini - Quand vous ajoutez une
ScrollViewà la page, elle devrait presque toujours être le premier composant dans le composant de route - Préférez
headerSearchBarOptionsdans les options Stack.Screen pour ajouter une barre de recherche - Utilisez le prop
<Text selectable />sur le texte contenant des données qui pourraient être copiées - Considérez le formatage des grands nombres comme 1,4M ou 38k
- Ne jamais utiliser d'éléments intrinsèques comme 'img' ou 'div' sauf dans une webview ou composant Expo DOM
Style
Suivez les directives d'interface humaine d'Apple.
Règles générales de style
- Préférez flex gap aux styles margin et padding
- Préférez padding à margin quand c'est possible
- Tenez toujours compte de la zone sûre, soit avec des headers de stack, des tabs, ou
contentInsetAdjustmentBehavior="automatic"sur ScrollView/FlatList - Assurez-vous que les insets de zone sûre haut et bas sont pris en compte
- Styles inline plutôt que StyleSheet.create sauf si la réutilisation de styles est plus rapide
- Ajoutez des animations d'entrée et de sortie pour les changements d'état
- Utilisez
{ borderCurve: 'continuous' }pour les coins arrondis sauf si vous créez une forme de capsule - UTILISEZ TOUJOURS un titre de pile de navigation au lieu d'un élément texte personnalisé sur la page
- Quand vous paddez une ScrollView, utilisez
contentContainerStylepadding et gap au lieu de padding sur la ScrollView elle-même (réduit le clipping) - CSS et Tailwind ne sont pas supportés - utilisez les styles inline
Style du texte
- Ajoutez le prop
selectableà chaque élément<Text/>affichant des données importantes ou des messages d'erreur - Les compteurs doivent utiliser
{ fontVariant: 'tabular-nums' }pour l'alignement
Ombres
Utilisez le prop de style CSS boxShadow. N'UTILISEZ JAMAIS les styles de shadow ou elevation hérités de React Native.
<View style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }} />
Les ombres 'inset' sont supportées.
Navigation
Link
Utilisez <Link href="/path" /> de 'expo-router' pour la navigation entre les routes.
import { Link } from 'expo-router';
// Lien basique
<Link href="/path" />
// Envelopper les composants personnalisés
<Link href="/path" asChild>
<Pressable>...</Pressable>
</Link>
Quand c'est possible, incluez un <Link.Preview> pour suivre les conventions iOS. Ajoutez des menus contextuels et des aperçus fréquemment pour améliorer la navigation.
Stack
- UTILISEZ TOUJOURS des fichiers
_layout.tsxpour définir les stacks - Utilisez Stack de 'expo-router/stack' pour les stacks de navigation natifs
Titre de la page
Définissez le titre de la page dans les options Stack.Screen :
<Stack.Screen options={{ title: "Home" }} />
Menus contextuels
Ajoutez des menus contextuels au long appui aux composants Link :
import { Link } from "expo-router";
<Link href="/settings" asChild>
<Link.Trigger>
<Pressable>
<Card />
</Pressable>
</Link.Trigger>
<Link.Menu>
<Link.MenuAction
title="Share"
icon="square.and.arrow.up"
onPress={handleSharePress}
/>
<Link.MenuAction
title="Block"
icon="nosign"
destructive
onPress={handleBlockPress}
/>
<Link.Menu title="More" icon="ellipsis">
<Link.MenuAction title="Copy" icon="doc.on.doc" onPress={() => {}} />
<Link.MenuAction
title="Delete"
icon="trash"
destructive
onPress={() => {}}
/>
</Link.Menu>
</Link.Menu>
</Link>;
Aperçus des liens
Utilisez les aperçus de liens fréquemment pour améliorer la navigation :
<Link href="/settings">
<Link.Trigger>
<Pressable>
<Card />
</Pressable>
</Link.Trigger>
<Link.Preview />
</Link>
L'aperçu de lien peut être utilisé avec les menus contextuels.
Modal
Présentez un écran comme une modal :
<Stack.Screen name="modal" options={{ presentation: "modal" }} />
Préférez cela à la construction d'un composant modal personnalisé.
Sheet
Présentez un écran comme une form sheet dynamique :
<Stack.Screen
name="sheet"
options={{
presentation: "formSheet",
sheetGrabberVisible: true,
sheetAllowedDetents: [0.5, 1.0],
contentStyle: { backgroundColor: "transparent" },
}}
/>
- L'utilisation de
contentStyle: { backgroundColor: "transparent" }rend le fond liquid glass sur iOS 26+.
Structure de route commune
Une disposition d'application standard avec tabs et stacks à l'intérieur de chaque tab :
app/
_layout.tsx — <NativeTabs />
(index,search)/
_layout.tsx — <Stack />
index.tsx — Liste principale
search.tsx — Vue de recherche
// app/_layout.tsx
import { NativeTabs, Icon, Label } from "expo-router/unstable-native-tabs";
import { Theme } from "../components/theme";
export default function Layout() {
return (
<Theme>
<NativeTabs>
<NativeTabs.Trigger name="(index)">
<Icon sf="list.dash" />
<Label>Items</Label>
</NativeTabs.Trigger>
<NativeTabs.Trigger name="(search)" role="search" />
</NativeTabs>
</Theme>
);
}
Créez une route de groupe partagée pour que les deux tabs puissent pousser les écrans communs :
// app/(index,search)/_layout.tsx
import { Stack } from "expo-router/stack";
import { PlatformColor } from "react-native";
export default function Layout({ segment }) {
const screen = segment.match(/\((.*)\)/)?.[1]!;
const titles: Record<string, string> = { index: "Items", search: "Search" };
return (
<Stack
screenOptions={{
headerTransparent: true,
headerShadowVisible: false,
headerLargeTitleShadowVisible: false,
headerLargeStyle: { backgroundColor: "transparent" },
headerTitleStyle: { color: PlatformColor("label") },
headerLargeTitle: true,
headerBlurEffect: "none",
headerBackButtonDisplayMode: "minimal",
}}
>
<Stack.Screen name={screen} options={{ title: titles[screen] }} />
<Stack.Screen name="i/[id]" options={{ headerLargeTitle: false }} />
</Stack>
);
}