building-native-ui

Par expo · skills

Guide complet pour créer de belles applications avec Expo Router. Couvre les fondamentaux, le style, les composants, la navigation, les animations, les patterns et les onglets natifs.

npx skills add https://github.com/expo/skills --skill building-native-ui

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 :

  1. Commencez par Expo Go : Exécutez npx expo start et scannez le code QR avec Expo Go
  2. Vérifiez que les fonctionnalités fonctionnent : Testez votre application en détail dans Expo Go
  3. 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-audio et non expo-av
  • expo-video et non expo-av
  • expo-image avec source="sf:name" pour les SF Symbols, pas expo-symbols ou @expo/vector-icons
  • react-native-safe-area-context et non react-native SafeAreaView
  • process.env.EXPO_OS et non Platform.OS
  • React.use et non React.useContext
  • Composant expo-image Image au lieu de l'élément intrinsèque img
  • expo-glass-effect pour 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 headerSearchBarOptions dans 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 contentContainerStyle padding 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.tsx pour 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>
  );
}

Skills similaires