Claude Skill

mobile-framework-react-native

React Native mobile development patterns - New Architecture (Fabric, TurboModules, JSI), component architecture, React Navigation 7+, FlashList v2 optimization, gestures with Reanimated 4, platform-specific code, React 19 features

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download agents-inc-skills-dist_plugins_mobile-framework-react-native_skills_mobile-framework-react-native-3a51ef5.zip · 23 KB
Part of agents-inc/skills — 130 skills

Install

skills CLI npx skills add https://github.com/agents-inc/skills/tree/main/dist/plugins/mobile-framework-react-native/skills/mobile-framework-react-native
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install agents-inc-skills@llmmart
Git git clone https://github.com/agents-inc/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole agents-inc/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

React Native Development Patterns

Quick Guide: Build cross-platform mobile apps with React Native's New Architecture (default since 0.76). Use FlashList for performant lists (or FlatList with proper optimization). Use type-safe navigation hooks with static or dynamic API. Keep components small, memoize callbacks passed to lists, and test on both platforms from day one.


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST use FlashList (preferred) or FlatList for lists with more than 20 items - NEVER ScrollView with .map() for long lists)

(You MUST memoize renderItem callbacks and use stable keyExtractor functions - avoid key props on FlashList items as it breaks recycling)

(You MUST use react-native-safe-area-context for safe areas - React Native's built-in SafeAreaView is deprecated in 0.81+ and will be removed)

(You MUST test on BOTH iOS AND Android from day one - platform differences cause bugs)

(You MUST use Platform.select() or platform-specific files for platform differences - shadows, fonts, and feedback differ)

(You MUST be aware the New Architecture is enabled by default since React Native 0.76 - Fabric, TurboModules, and bridgeless mode)

</critical_requirements>


Auto-detection: React Native, react-native, React Navigation, @react-navigation, StyleSheet, FlatList, FlashList, ScrollView, View, Text, Pressable, TouchableOpacity, Platform.OS, Platform.select, SafeAreaView, KeyboardAvoidingView, Reanimated, Gesture Handler, TurboModules, Fabric, JSI, New Architecture

When to use:

  • Building cross-platform iOS and Android mobile applications
  • Creating native mobile UIs with React patterns
  • Implementing mobile navigation with stack, tab, or drawer patterns
  • Optimizing list performance with FlashList/FlatList and virtualization
  • Adding gestures and animations with Reanimated 4
  • Handling platform-specific code for iOS vs Android differences
  • Working with React Native's New Architecture (Fabric, TurboModules, JSI)

Key patterns covered:

  • New Architecture fundamentals (Fabric, TurboModules, JSI, bridgeless mode)
  • Component architecture with accessibility props and platform-specific patterns
  • React Navigation 7+ with type-safe hooks, static API, and auth flows
  • FlashList/FlatList optimization with memoization and cell recycling
  • Platform-specific code with Platform.select and file extensions
  • Safe area and keyboard handling
  • React 19 features (React Native 0.78+): useOptimistic, use, ref as props

When NOT to use:

  • Web-only React applications (use standard React patterns)
  • React Native Web hybrid apps (requires additional considerations)
  • Flutter, Swift, or Kotlin native development

Detailed Resources:




<red_flags>

RED FLAGS

High Priority Issues:

  • Using ScrollView + map() for lists with 50+ items - causes severe performance, use FlashList or FlatList
  • Adding key prop to FlashList items - BREAKS cell recycling, eliminates FlashList's main benefit
  • Using React Native's built-in SafeAreaView - deprecated in 0.81+, use react-native-safe-area-context
  • Not testing on both platforms - iOS/Android differences compound; test daily on both
  • Inline functions in FlatList/FlashList renderItem - creates new function every render, breaks memoization
  • Using Reanimated 4 with old architecture - Reanimated 4.x is New Architecture ONLY

Medium Priority Issues:

  • Hardcoded colors/spacing instead of constants - breaks consistency, makes theming impossible
  • Not using Platform.select for shadows - iOS shadow props don't work on Android (use elevation)
  • Missing keyboard handling on forms - keyboard covers inputs without KeyboardAvoidingView
  • Using TouchableOpacity everywhere - Pressable is more flexible and supports android_ripple

Gotchas & Edge Cases:

  • Android fontWeight only supports 'normal' and 'bold' reliably - 100-900 values may not work
  • iOS shadow props are completely ignored on Android - must use elevation for Android shadows
  • StatusBar backgroundColor only works on Android - iOS uses translucent status bar
  • FlatList onEndReached fires immediately if data fits screen - use onEndReachedThreshold carefully
  • KeyboardAvoidingView behavior differs: 'padding' for iOS, 'height' for Android
  • React Native doesn't have CSS cascade - each component must have complete styles
  • Text must be wrapped in <Text> component - raw strings cause crashes
  • New Architecture enabled by default in 0.76+ - some older libraries may need updates
  • FlashList v2 is New Architecture only - use v1 or FlatList if on old architecture
  • React Native 0.78+ uses React 19 - propTypes removed, forwardRef optional
  • React 19 adoption in recent SDK versions - check for breaking changes in your dependencies
  • Android 15/16 enforces edge-to-edge - must handle safe areas properly
  • Reanimated 4 requires react-native-worklets - Reanimated 3 will not work with it installed
  • boxShadow and filter props are New Architecture only - not available on legacy architecture
  • Reanimated 4: withSpring no longer uses restDisplacementThreshold/restSpeedThreshold - replaced by energyThreshold

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md

(You MUST use FlashList (preferred) or FlatList for lists with more than 20 items - NEVER ScrollView with .map() for long lists)

(You MUST memoize renderItem callbacks and use stable keyExtractor functions - avoid key props on FlashList items as it breaks recycling)

(You MUST use react-native-safe-area-context for safe areas - React Native's built-in SafeAreaView is deprecated in 0.81+ and will be removed)

(You MUST test on BOTH iOS AND Android from day one - platform differences cause bugs)

(You MUST use Platform.select() or platform-specific files for platform differences - shadows, fonts, and feedback differ)

(You MUST be aware the New Architecture is enabled by default since React Native 0.76 - Fabric, TurboModules, and bridgeless mode)

Failure to follow these rules will result in poor performance, platform-specific bugs, and broken UX on mobile devices.

</critical_reminders>

Files (skills)
  • examples
    • core.md 9.4 KB
      # React Native - Core Patterns
      
      > Core component architecture and platform patterns. See [SKILL.md](../SKILL.md) for decision guidance.
      
      **Prerequisites**: Familiarity with React component patterns and TypeScript.
      
      ---
      
      ## Pattern 1: Component with Variants, Accessibility, and Loading
      
      ```typescript
      import { forwardRef, useCallback, useMemo, type ReactNode } from "react";
      import {
        View,
        Text,
        Pressable,
        StyleSheet,
        ActivityIndicator,
        type ViewStyle,
        type TextStyle,
      } from "react-native";
      
      // Design tokens as constants
      const COLORS = {
        primary: "#007AFF",
        secondary: "#5856D6",
        ghost: "transparent",
        text: "#FFFFFF",
        textGhost: "#007AFF",
        disabled: "rgba(0,0,0,0.3)",
      } as const;
      
      const SIZES = {
        sm: { paddingVertical: 8, paddingHorizontal: 12, fontSize: 14 },
        md: { paddingVertical: 12, paddingHorizontal: 16, fontSize: 16 },
        lg: { paddingVertical: 16, paddingHorizontal: 24, fontSize: 18 },
      } as const;
      
      interface ButtonProps {
        children: ReactNode;
        variant?: "primary" | "secondary" | "ghost";
        size?: "sm" | "md" | "lg";
        disabled?: boolean;
        loading?: boolean;
        onPress: () => void;
        style?: ViewStyle;
        textStyle?: TextStyle;
        testID?: string;
      }
      
      export const Button = forwardRef<View, ButtonProps>(
        (
          {
            children,
            variant = "primary",
            size = "md",
            disabled = false,
            loading = false,
            onPress,
            style,
            textStyle,
            testID,
          },
          ref
        ) => {
          const isDisabled = disabled || loading;
      
          const buttonStyle = useMemo(
            () => [
              styles.base,
              {
                backgroundColor: variant === "ghost" ? COLORS.ghost : COLORS[variant],
                paddingVertical: SIZES[size].paddingVertical,
                paddingHorizontal: SIZES[size].paddingHorizontal,
              },
              variant === "ghost" && styles.ghostBorder,
              isDisabled && styles.disabled,
              style,
            ],
            [variant, size, isDisabled, style]
          );
      
          const labelStyle = useMemo(
            () => [
              styles.text,
              { fontSize: SIZES[size].fontSize },
              variant === "ghost" && styles.ghostText,
              textStyle,
            ],
            [variant, size, textStyle]
          );
      
          const handlePress = useCallback(() => {
            if (!isDisabled) {
              onPress();
            }
          }, [isDisabled, onPress]);
      
          return (
            <Pressable
              ref={ref}
              style={buttonStyle}
              onPress={handlePress}
              disabled={isDisabled}
              testID={testID}
              accessibilityRole="button"
              accessibilityState={{ disabled: isDisabled }}
              accessibilityLabel={typeof children === "string" ? children : undefined}
            >
              {loading ? (
                <ActivityIndicator color={variant === "ghost" ? COLORS.primary : COLORS.text} />
              ) : (
                <Text style={labelStyle}>{children}</Text>
              )}
            </Pressable>
          );
        }
      );
      
      Button.displayName = "Button";
      
      const styles = StyleSheet.create({
        base: {
          borderRadius: 8,
          alignItems: "center",
          justifyContent: "center",
          flexDirection: "row",
        },
        text: {
          color: COLORS.text,
          fontWeight: "600",
        },
        ghostBorder: {
          borderWidth: 1,
          borderColor: COLORS.primary,
        },
        ghostText: {
          color: COLORS.textGhost,
        },
        disabled: {
          opacity: 0.5,
        },
      });
      ```
      
      **Why good:** forwardRef for parent ref access, accessibilityRole/State for screen readers, useMemo prevents style object recreation, testID for E2E testing, named constants, loading state built-in
      
      ---
      
      ## Pattern 2: Compound Component with Reanimated
      
      ```typescript
      import {
        createContext,
        useContext,
        useState,
        useCallback,
        useMemo,
        type ReactNode,
      } from "react";
      import { View, Text, Pressable, StyleSheet } from "react-native";
      import Animated, {
        useAnimatedStyle,
        withTiming,
      } from "react-native-reanimated";
      
      // Types
      interface AccordionContextValue {
        expandedId: string | null;
        toggle: (id: string) => void;
      }
      
      interface AccordionItemContextValue {
        id: string;
        isExpanded: boolean;
      }
      
      // Contexts
      const AccordionContext = createContext<AccordionContextValue | null>(null);
      const AccordionItemContext = createContext<AccordionItemContextValue | null>(null);
      
      // Hooks
      function useAccordion() {
        const context = useContext(AccordionContext);
        if (!context) {
          throw new Error("Accordion components must be used within Accordion.Root");
        }
        return context;
      }
      
      function useAccordionItem() {
        const context = useContext(AccordionItemContext);
        if (!context) {
          throw new Error("AccordionItem components must be used within Accordion.Item");
        }
        return context;
      }
      
      // Root Component
      function AccordionRoot({ children, defaultExpanded }: { children: ReactNode; defaultExpanded?: string }) {
        const [expandedId, setExpandedId] = useState<string | null>(defaultExpanded ?? null);
      
        const toggle = useCallback((id: string) => {
          setExpandedId((prev) => (prev === id ? null : id));
        }, []);
      
        const value = useMemo(() => ({ expandedId, toggle }), [expandedId, toggle]);
      
        return (
          <AccordionContext.Provider value={value}>
            <View style={styles.root}>{children}</View>
          </AccordionContext.Provider>
        );
      }
      
      // Trigger Component with animated icon
      function AccordionTrigger({ children }: { children: ReactNode }) {
        const { toggle } = useAccordion();
        const { id, isExpanded } = useAccordionItem();
      
        const handlePress = useCallback(() => toggle(id), [toggle, id]);
      
        const iconStyle = useAnimatedStyle(() => ({
          transform: [{ rotate: withTiming(isExpanded ? "180deg" : "0deg") }],
        }));
      
        return (
          <Pressable
            onPress={handlePress}
            style={styles.trigger}
            accessibilityRole="button"
            accessibilityState={{ expanded: isExpanded }}
          >
            <Text style={styles.triggerText}>{children}</Text>
            <Animated.Text style={[styles.icon, iconStyle]}>▼</Animated.Text>
          </Pressable>
        );
      }
      
      // Item and Content components follow same pattern
      function AccordionItem({ id, children }: { id: string; children: ReactNode }) {
        const { expandedId } = useAccordion();
        const isExpanded = expandedId === id;
        const value = useMemo(() => ({ id, isExpanded }), [id, isExpanded]);
      
        return (
          <AccordionItemContext.Provider value={value}>
            <View style={styles.item}>{children}</View>
          </AccordionItemContext.Provider>
        );
      }
      
      function AccordionContent({ children }: { children: ReactNode }) {
        const { isExpanded } = useAccordionItem();
        if (!isExpanded) return null;
      
        return <View style={styles.content}>{children}</View>;
      }
      
      // Export as compound component
      export const Accordion = {
        Root: AccordionRoot,
        Item: AccordionItem,
        Trigger: AccordionTrigger,
        Content: AccordionContent,
      };
      
      const styles = StyleSheet.create({
        root: {
          borderRadius: 8,
          overflow: "hidden",
          borderWidth: 1,
          borderColor: "#E5E5E5",
        },
        item: {
          borderBottomWidth: 1,
          borderBottomColor: "#E5E5E5",
        },
        trigger: {
          flexDirection: "row",
          alignItems: "center",
          justifyContent: "space-between",
          padding: 16,
          backgroundColor: "#FAFAFA",
        },
        triggerText: {
          fontSize: 16,
          fontWeight: "600",
          color: "#1A1A1A",
        },
        icon: {
          fontSize: 12,
          color: "#666",
        },
        content: {
          padding: 16,
          backgroundColor: "#FFFFFF",
        },
      });
      
      // Usage
      function FAQScreen() {
        return (
          <Accordion.Root defaultExpanded="q1">
            <Accordion.Item id="q1">
              <Accordion.Trigger>What is React Native?</Accordion.Trigger>
              <Accordion.Content>
                <Text>React Native is a framework for building native mobile apps...</Text>
              </Accordion.Content>
            </Accordion.Item>
            <Accordion.Item id="q2">
              <Accordion.Trigger>How does it work?</Accordion.Trigger>
              <Accordion.Content>
                <Text>React Native renders to native components...</Text>
              </Accordion.Content>
            </Accordion.Item>
          </Accordion.Root>
        );
      }
      ```
      
      **Why good:** Context-based compound component with Reanimated animations, accessibilityState tracks expanded state, throw on missing provider catches misuse early
      
      ---
      
      ## Pattern 3: Platform-Specific File Splitting
      
      When platform differences are significant (different haptic APIs, different UI feedback), use platform-specific file extensions.
      
      ```
      components/
      ├── button/
      │   ├── button.tsx           # Shared types/logic
      │   ├── button.ios.tsx       # iOS-specific implementation
      │   ├── button.android.tsx   # Android-specific implementation
      │   └── index.ts             # Re-exports platform file
      ```
      
      ```typescript
      // button.ios.tsx
      import { Pressable, Text } from "react-native";
      import * as Haptics from "expo-haptics";
      
      export function Button({ onPress, children }: ButtonProps) {
        const handlePress = () => {
          Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
          onPress();
        };
      
        return (
          <Pressable onPress={handlePress} style={styles.button}>
            <Text style={styles.text}>{children}</Text>
          </Pressable>
        );
      }
      
      // button.android.tsx
      import { Pressable, Text } from "react-native";
      import ReactNativeHapticFeedback from "react-native-haptic-feedback";
      
      export function Button({ onPress, children }: ButtonProps) {
        const handlePress = () => {
          ReactNativeHapticFeedback.trigger("impactMedium");
          onPress();
        };
      
        return (
          <Pressable
            onPress={handlePress}
            android_ripple={{ color: "rgba(0,0,0,0.1)" }}
            style={styles.button}
          >
            <Text style={styles.text}>{children}</Text>
          </Pressable>
        );
      }
      ```
      
      **Why good:** Each platform uses native haptic API, Android gets ripple effect, Metro bundler auto-selects correct file based on platform
      
    • navigation.md 14.2 KB
      # React Native - Navigation Patterns
      
      > Type-safe navigation, authentication flows, and deep linking. See [core.md](core.md) for component patterns.
      
      **Prerequisites**: Familiarity with React Navigation concepts (stack, tab, drawer navigators).
      
      ---
      
      ## Type-Safe Navigation Setup
      
      ```typescript
      // navigation/types.ts
      import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
      import type { BottomTabNavigationProp } from "@react-navigation/bottom-tabs";
      import type {
        CompositeNavigationProp,
        RouteProp,
      } from "@react-navigation/native";
      
      // Define param lists for each navigator
      export type RootStackParamList = {
        Auth: undefined;
        Main: undefined;
        Modal: { title: string };
      };
      
      export type AuthStackParamList = {
        Login: undefined;
        Register: undefined;
        ForgotPassword: { email?: string };
      };
      
      export type MainTabParamList = {
        Home: undefined;
        Search: { query?: string };
        Profile: undefined;
      };
      
      export type HomeStackParamList = {
        HomeScreen: undefined;
        ProductDetail: { productId: string };
        CategoryList: { categoryId: string; categoryName: string };
      };
      
      // Composite navigation types for nested navigators
      export type HomeScreenNavigationProp = CompositeNavigationProp<
        NativeStackNavigationProp<HomeStackParamList, "HomeScreen">,
        CompositeNavigationProp<
          BottomTabNavigationProp<MainTabParamList>,
          NativeStackNavigationProp<RootStackParamList>
        >
      >;
      
      // Route types
      export type ProductDetailRouteProp = RouteProp<
        HomeStackParamList,
        "ProductDetail"
      >;
      ```
      
      ---
      
      ## Type-Safe Navigation Hooks
      
      ```typescript
      // navigation/hooks.ts
      import { useNavigation, useRoute } from "@react-navigation/native";
      import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
      import type { RouteProp } from "@react-navigation/native";
      import type {
        RootStackParamList,
        AuthStackParamList,
        HomeStackParamList,
      } from "./types";
      
      // Typed navigation hooks
      export function useRootNavigation() {
        return useNavigation<NativeStackNavigationProp<RootStackParamList>>();
      }
      
      export function useAuthNavigation() {
        return useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
      }
      
      export function useHomeNavigation() {
        return useNavigation<NativeStackNavigationProp<HomeStackParamList>>();
      }
      
      // Typed route hook
      export function useTypedRoute<
        ParamList extends Record<string, object | undefined>,
        RouteName extends keyof ParamList
      >() {
        return useRoute<RouteProp<ParamList, RouteName>>();
      }
      
      // Usage in component
      function ProductDetailScreen() {
        const navigation = useHomeNavigation();
        const route = useTypedRoute<HomeStackParamList, "ProductDetail">();
      
        const { productId } = route.params;
      
        const handleGoBack = () => {
          navigation.goBack();
        };
      
        const handleNavigateToCategory = (categoryId: string, categoryName: string) => {
          navigation.navigate("CategoryList", { categoryId, categoryName });
        };
      
        return (
          <View>
            <Text>Product: {productId}</Text>
          </View>
        );
      }
      ```
      
      ---
      
      ## Authentication Flow Pattern
      
      ```typescript
      // navigation/root-navigator.tsx
      import { createNativeStackNavigator } from "@react-navigation/native-stack";
      import { useAuth } from "../hooks/use-auth";
      import { AuthNavigator } from "./auth-navigator";
      import { MainNavigator } from "./main-navigator";
      import { SplashScreen } from "../screens/splash-screen";
      import type { RootStackParamList } from "./types";
      
      const Stack = createNativeStackNavigator<RootStackParamList>();
      
      export function RootNavigator() {
        const { isAuthenticated, isLoading } = useAuth();
      
        // Show splash while checking auth state
        if (isLoading) {
          return <SplashScreen />;
        }
      
        return (
          <Stack.Navigator screenOptions={{ headerShown: false }}>
            {isAuthenticated ? (
              <Stack.Screen name="Main" component={MainNavigator} />
            ) : (
              <Stack.Screen name="Auth" component={AuthNavigator} />
            )}
          </Stack.Navigator>
        );
      }
      
      // navigation/auth-navigator.tsx
      import { createNativeStackNavigator } from "@react-navigation/native-stack";
      import { LoginScreen } from "../screens/auth/login-screen";
      import { RegisterScreen } from "../screens/auth/register-screen";
      import { ForgotPasswordScreen } from "../screens/auth/forgot-password-screen";
      import type { AuthStackParamList } from "./types";
      
      const Stack = createNativeStackNavigator<AuthStackParamList>();
      
      export function AuthNavigator() {
        return (
          <Stack.Navigator
            screenOptions={{
              headerShown: true,
              headerBackTitleVisible: false,
            }}
          >
            <Stack.Screen
              name="Login"
              component={LoginScreen}
              options={{ headerShown: false }}
            />
            <Stack.Screen
              name="Register"
              component={RegisterScreen}
              options={{ title: "Create Account" }}
            />
            <Stack.Screen
              name="ForgotPassword"
              component={ForgotPasswordScreen}
              options={{ title: "Reset Password" }}
            />
          </Stack.Navigator>
        );
      }
      ```
      
      ---
      
      ## Tab Navigator with Nested Stacks
      
      ```typescript
      // navigation/main-navigator.tsx
      import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
      import { createNativeStackNavigator } from "@react-navigation/native-stack";
      import { HomeScreen } from "../screens/home/home-screen";
      import { ProductDetailScreen } from "../screens/home/product-detail-screen";
      import { SearchScreen } from "../screens/search/search-screen";
      import { ProfileScreen } from "../screens/profile/profile-screen";
      import type { MainTabParamList, HomeStackParamList } from "./types";
      
      // Home Stack (nested in tab)
      const HomeStack = createNativeStackNavigator<HomeStackParamList>();
      
      function HomeStackNavigator() {
        return (
          <HomeStack.Navigator>
            <HomeStack.Screen
              name="HomeScreen"
              component={HomeScreen}
              options={{ headerShown: false }}
            />
            <HomeStack.Screen
              name="ProductDetail"
              component={ProductDetailScreen}
              options={({ route }) => ({
                title: "Product Details",
              })}
            />
          </HomeStack.Navigator>
        );
      }
      
      // Tab Navigator
      const Tab = createBottomTabNavigator<MainTabParamList>();
      
      export function MainNavigator() {
        return (
          <Tab.Navigator
            screenOptions={{
              headerShown: false,
              tabBarActiveTintColor: "#007AFF",
              tabBarInactiveTintColor: "#8E8E93",
            }}
          >
            <Tab.Screen
              name="Home"
              component={HomeStackNavigator}
              options={{
                tabBarLabel: "Home",
                tabBarIcon: ({ color, size }) => (
                  <TabIcon name="home" color={color} size={size} />
                ),
              }}
            />
            <Tab.Screen
              name="Search"
              component={SearchScreen}
              options={{
                tabBarLabel: "Search",
                tabBarIcon: ({ color, size }) => (
                  <TabIcon name="search" color={color} size={size} />
                ),
              }}
            />
            <Tab.Screen
              name="Profile"
              component={ProfileScreen}
              options={{
                tabBarLabel: "Profile",
                tabBarIcon: ({ color, size }) => (
                  <TabIcon name="person" color={color} size={size} />
                ),
              }}
            />
          </Tab.Navigator>
        );
      }
      ```
      
      ---
      
      ## Modal Navigation Pattern
      
      ```typescript
      // navigation/root-navigator.tsx - with modals
      import { createNativeStackNavigator } from "@react-navigation/native-stack";
      import { ModalScreen } from "../screens/modal-screen";
      
      const Stack = createNativeStackNavigator<RootStackParamList>();
      
      export function RootNavigator() {
        const { isAuthenticated } = useAuth();
      
        return (
          <Stack.Navigator>
            <Stack.Group screenOptions={{ headerShown: false }}>
              {isAuthenticated ? (
                <Stack.Screen name="Main" component={MainNavigator} />
              ) : (
                <Stack.Screen name="Auth" component={AuthNavigator} />
              )}
            </Stack.Group>
      
            {/* Modal screens accessible from anywhere */}
            <Stack.Group
              screenOptions={{
                presentation: "modal",
                headerShown: true,
              }}
            >
              <Stack.Screen
                name="Modal"
                component={ModalScreen}
                options={({ route }) => ({
                  title: route.params.title,
                })}
              />
            </Stack.Group>
          </Stack.Navigator>
        );
      }
      
      // Opening modal from anywhere
      function SomeScreen() {
        const navigation = useRootNavigation();
      
        const openModal = () => {
          navigation.navigate("Modal", { title: "My Modal" });
        };
      
        return (
          <Button onPress={openModal}>Open Modal</Button>
        );
      }
      ```
      
      ---
      
      ## useFocusEffect for Resource Management
      
      ```typescript
      import { useCallback } from "react";
      import { useFocusEffect } from "@react-navigation/native";
      
      // WebSocket connection management
      function ChatScreen({ roomId }: { roomId: string }) {
        useFocusEffect(
          useCallback(() => {
            // Setup: Connect when screen is focused
            const ws = new WebSocket(`wss://chat.example.com/rooms/${roomId}`);
      
            ws.onopen = () => {
              console.log("Connected to chat");
            };
      
            ws.onmessage = (event) => {
              // Handle incoming messages
            };
      
            // Cleanup: Disconnect when screen loses focus
            return () => {
              ws.close();
            };
          }, [roomId])
        );
      
        return <ChatUI />;
      }
      
      // Analytics tracking
      function ProductScreen({ productId }: { productId: string }) {
        useFocusEffect(
          useCallback(() => {
            // Track screen view when focused
            analytics.trackScreenView("ProductScreen", { productId });
      
            const startTime = Date.now();
      
            // Track time spent when leaving
            return () => {
              const timeSpent = Date.now() - startTime;
              analytics.trackTimeSpent("ProductScreen", { productId, timeSpent });
            };
          }, [productId])
        );
      
        return <ProductDetails productId={productId} />;
      }
      
      // Polling data while focused
      function NotificationsScreen() {
        const [notifications, setNotifications] = useState([]);
      
        useFocusEffect(
          useCallback(() => {
            // Start polling when focused
            const fetchNotifications = async () => {
              const data = await api.getNotifications();
              setNotifications(data);
            };
      
            fetchNotifications();
            const interval = setInterval(fetchNotifications, 30000);
      
            // Stop polling when unfocused
            return () => {
              clearInterval(interval);
            };
          }, [])
        );
      
        return <NotificationList notifications={notifications} />;
      }
      ```
      
      ---
      
      ## Screen Preloading Pattern
      
      ```typescript
      import { useNavigation } from "@react-navigation/native";
      import { useCallback } from "react";
      
      function ProductList({ products }: { products: Product[] }) {
        const navigation = useNavigation();
      
        // Preload product detail screen when user hovers/long-presses
        const handlePreload = useCallback(
          (productId: string) => {
            // React Navigation 7+ supports preloading
            if ("preload" in navigation) {
              (navigation as any).preload("ProductDetail", { productId });
            }
          },
          [navigation]
        );
      
        const renderItem = useCallback(
          ({ item }: { item: Product }) => (
            <ProductCard
              product={item}
              onPress={() =>
                navigation.navigate("ProductDetail", { productId: item.id })
              }
              onLongPress={() => handlePreload(item.id)}
            />
          ),
          [navigation, handlePreload]
        );
      
        return (
          <FlatList
            data={products}
            renderItem={renderItem}
            keyExtractor={(item) => item.id}
          />
        );
      }
      ```
      
      ---
      
      ## Deep Linking Configuration
      
      ```typescript
      // navigation/linking.ts
      import type { LinkingOptions } from "@react-navigation/native";
      import type { RootStackParamList } from "./types";
      
      export const linking: LinkingOptions<RootStackParamList> = {
        prefixes: ["myapp://", "https://myapp.com"],
      
        config: {
          screens: {
            Auth: {
              screens: {
                Login: "login",
                Register: "register",
                ForgotPassword: "forgot-password",
              },
            },
            Main: {
              screens: {
                Home: {
                  screens: {
                    HomeScreen: "",
                    ProductDetail: "product/:productId",
                    CategoryList: "category/:categoryId",
                  },
                },
                Search: "search",
                Profile: "profile",
              },
            },
            Modal: "modal/:title",
          },
        },
      
        // Custom URL parsing
        getStateFromPath: (path, config) => {
          // Handle custom URL formats
          if (path.startsWith("/p/")) {
            const productId = path.replace("/p/", "");
            return {
              routes: [
                {
                  name: "Main",
                  state: {
                    routes: [
                      {
                        name: "Home",
                        state: {
                          routes: [
                            { name: "HomeScreen" },
                            { name: "ProductDetail", params: { productId } },
                          ],
                        },
                      },
                    ],
                  },
                },
              ],
            };
          }
      
          // Default behavior
          return undefined;
        },
      };
      
      // App.tsx
      import { NavigationContainer } from "@react-navigation/native";
      import { linking } from "./navigation/linking";
      
      function App() {
        return (
          <NavigationContainer linking={linking}>
            <RootNavigator />
          </NavigationContainer>
        );
      }
      ```
      
      ---
      
      ## Navigation State Persistence
      
      ```typescript
      // Persist navigation state across app restarts
      import AsyncStorage from "@react-native-async-storage/async-storage";
      import { NavigationContainer, type NavigationState } from "@react-navigation/native";
      import { useCallback, useEffect, useState } from "react";
      
      const NAVIGATION_STATE_KEY = "NAVIGATION_STATE";
      
      function App() {
        const [isReady, setIsReady] = useState(false);
        const [initialState, setInitialState] = useState<NavigationState | undefined>();
      
        useEffect(() => {
          const restoreState = async () => {
            try {
              const savedStateString = await AsyncStorage.getItem(NAVIGATION_STATE_KEY);
              const state = savedStateString ? JSON.parse(savedStateString) : undefined;
              setInitialState(state);
            } finally {
              setIsReady(true);
            }
          };
      
          if (!isReady) {
            restoreState();
          }
        }, [isReady]);
      
        const onStateChange = useCallback((state: NavigationState | undefined) => {
          if (state) {
            AsyncStorage.setItem(NAVIGATION_STATE_KEY, JSON.stringify(state));
          }
        }, []);
      
        if (!isReady) {
          return <SplashScreen />;
        }
      
        return (
          <NavigationContainer
            initialState={initialState}
            onStateChange={onStateChange}
          >
            <RootNavigator />
          </NavigationContainer>
        );
      }
      ```
      
    • performance.md 15.5 KB
      # React Native - Performance Patterns
      
      > FlashList/FlatList optimization, memoization, lazy loading, and profiling. See [core.md](core.md) for component patterns.
      
      **Prerequisites**: Understand list virtualization concepts and React.memo basics.
      
      ---
      
      ## Pattern 1: FlashList (Recommended for New Architecture)
      
      FlashList v2 provides superior performance through cell recycling instead of virtualization. **FlashList v2 is New Architecture only.** Key improvements: no more estimatedItemSize required, up to 50% reduced blank area, built-in masonry layout support, and automatic item resizing.
      
      ```typescript
      import { FlashList } from "@shopify/flash-list";
      import { memo, useCallback } from "react";
      import { View, Text, Pressable, StyleSheet } from "react-native";
      
      // Constants
      const ITEM_HEIGHT = 80;
      
      interface Product {
        id: string;
        name: string;
        price: number;
        category: string;
      }
      
      interface ProductItemProps {
        item: Product;
        onPress: (id: string) => void;
      }
      
      // Memoized item component - CRITICAL: Do NOT add key prop (breaks recycling)
      const ProductItem = memo(function ProductItem({ item, onPress }: ProductItemProps) {
        const handlePress = useCallback(() => {
          onPress(item.id);
        }, [item.id, onPress]);
      
        return (
          <Pressable onPress={handlePress} style={styles.item}>
            <View style={styles.details}>
              <Text style={styles.name} numberOfLines={1}>
                {item.name}
              </Text>
              <Text style={styles.price}>${item.price.toFixed(2)}</Text>
            </View>
          </Pressable>
        );
      });
      
      // Main list component with FlashList
      interface ProductListProps {
        products: Product[];
        onProductPress: (id: string) => void;
        onEndReached?: () => void;
      }
      
      export function ProductListFlash({
        products,
        onProductPress,
        onEndReached,
      }: ProductListProps) {
        // Stable renderItem with useCallback
        const renderItem = useCallback(
          ({ item }: { item: Product }) => (
            <ProductItem item={item} onPress={onProductPress} />
          ),
          [onProductPress]
        );
      
        // Use getItemType for different item types (improves recycling)
        const getItemType = useCallback((item: Product) => {
          return item.category; // Items of same category share recycling pool
        }, []);
      
        return (
          <FlashList
            data={products}
            renderItem={renderItem}
            // FlashList v2: estimatedItemSize is OPTIONAL (auto-calculates from actual measurements)
            // FlashList v1: estimatedItemSize is REQUIRED for performance
            // Providing it in v2 can still help with initial render
            estimatedItemSize={ITEM_HEIGHT}
            // Use getItemType for heterogeneous lists
            getItemType={getItemType}
            // Performance optimizations
            onEndReached={onEndReached}
            onEndReachedThreshold={0.5}
            showsVerticalScrollIndicator={false}
          />
        );
      }
      
      const styles = StyleSheet.create({
        item: {
          height: ITEM_HEIGHT,
          flexDirection: "row",
          alignItems: "center",
          paddingHorizontal: 16,
          backgroundColor: "#FFFFFF",
        },
        details: {
          flex: 1,
        },
        name: {
          fontSize: 16,
          fontWeight: "600",
          color: "#1A1A1A",
        },
        price: {
          fontSize: 14,
          color: "#007AFF",
          marginTop: 4,
        },
      });
      ```
      
      **Why FlashList v2 is better:**
      
      - Cell recycling instead of virtualization (reuses component instances)
      - Up to 50% less blank area while scrolling (v2 on New Architecture)
      - Maintains 60 FPS even with complex items
      - Automatic item sizing in v2 (no estimatedItemSize required - measures real items)
      - Built-in masonry layout support via `overrideItemLayout` prop
      - `maintainVisibleContentPosition` enabled by default (no layout jumps)
      - Items can be dynamically resized without issues
      
      ---
      
      ## Pattern 2: Optimized FlatList
      
      ```typescript
      import { memo, useCallback, useMemo } from "react";
      import {
        FlatList,
        View,
        Text,
        Pressable,
        Image,
        StyleSheet,
        Platform,
        type ListRenderItem,
      } from "react-native";
      
      // Constants
      const ITEM_HEIGHT = 80;
      const SEPARATOR_HEIGHT = 1;
      const WINDOW_SIZE = 5;
      const MAX_TO_RENDER_PER_BATCH = 10;
      const INITIAL_NUM_TO_RENDER = 10;
      const ON_END_REACHED_THRESHOLD = 0.5;
      
      interface Product {
        id: string;
        name: string;
        price: number;
        imageUrl: string;
      }
      
      interface ProductItemProps {
        item: Product;
        onPress: (id: string) => void;
      }
      
      // Memoized item component - CRITICAL for performance
      const ProductItem = memo(function ProductItem({ item, onPress }: ProductItemProps) {
        const handlePress = useCallback(() => {
          onPress(item.id);
        }, [item.id, onPress]);
      
        return (
          <Pressable onPress={handlePress} style={styles.item}>
            <Image
              source={{ uri: item.imageUrl }}
              style={styles.image}
              resizeMode="cover"
            />
            <View style={styles.details}>
              <Text style={styles.name} numberOfLines={1}>
                {item.name}
              </Text>
              <Text style={styles.price}>${item.price.toFixed(2)}</Text>
            </View>
          </Pressable>
        );
      });
      
      // Separator component
      const ItemSeparator = memo(function ItemSeparator() {
        return <View style={styles.separator} />;
      });
      
      // Main list component
      interface ProductListProps {
        products: Product[];
        onProductPress: (id: string) => void;
        onEndReached?: () => void;
        isLoadingMore?: boolean;
      }
      
      export function ProductList({
        products,
        onProductPress,
        onEndReached,
        isLoadingMore = false,
      }: ProductListProps) {
        const renderItem: ListRenderItem<Product> = useCallback(
          ({ item }) => <ProductItem item={item} onPress={onProductPress} />,
          [onProductPress]
        );
      
        const keyExtractor = useCallback((item: Product) => item.id, []);
      
        // getItemLayout for fixed-height items (MAJOR performance win)
        const getItemLayout = useCallback(
          (_data: Product[] | null | undefined, index: number) => ({
            length: ITEM_HEIGHT + SEPARATOR_HEIGHT,
            offset: (ITEM_HEIGHT + SEPARATOR_HEIGHT) * index,
            index,
          }),
          []
        );
      
        const ListFooter = useMemo(() => {
          if (!isLoadingMore) return null;
          return (
            <View style={styles.footer}>
              <ActivityIndicator size="small" color="#007AFF" />
            </View>
          );
        }, [isLoadingMore]);
      
        return (
          <FlatList
            data={products}
            renderItem={renderItem}
            keyExtractor={keyExtractor}
            getItemLayout={getItemLayout}
            ItemSeparatorComponent={ItemSeparator}
            ListFooterComponent={ListFooter}
            windowSize={WINDOW_SIZE}
            maxToRenderPerBatch={MAX_TO_RENDER_PER_BATCH}
            initialNumToRender={INITIAL_NUM_TO_RENDER}
            removeClippedSubviews={Platform.OS === "android"}
            showsVerticalScrollIndicator={false}
            onEndReached={onEndReached}
            onEndReachedThreshold={ON_END_REACHED_THRESHOLD}
          />
        );
      }
      
      const styles = StyleSheet.create({
        item: {
          height: ITEM_HEIGHT,
          flexDirection: "row",
          alignItems: "center",
          paddingHorizontal: 16,
          backgroundColor: "#FFFFFF",
        },
        image: {
          width: 60,
          height: 60,
          borderRadius: 8,
          backgroundColor: "#F2F2F7",
        },
        details: {
          flex: 1,
          marginLeft: 12,
        },
        name: {
          fontSize: 16,
          fontWeight: "600",
          color: "#1A1A1A",
        },
        price: {
          fontSize: 14,
          color: "#007AFF",
          marginTop: 4,
        },
        separator: {
          height: SEPARATOR_HEIGHT,
          backgroundColor: "#E5E5EA",
          marginLeft: 88,
        },
        footer: {
          paddingVertical: 20,
          alignItems: "center",
        },
      });
      ```
      
      ---
      
      ## Pattern 3: Memoization Patterns
      
      ```typescript
      import { memo, useMemo, useCallback, useRef } from "react";
      
      // 1. React.memo for list items
      interface UserCardProps {
        user: User;
        onPress: (id: string) => void;
      }
      
      const UserCard = memo(function UserCard({ user, onPress }: UserCardProps) {
        const handlePress = useCallback(() => {
          onPress(user.id);
        }, [user.id, onPress]);
      
        return (
          <Pressable onPress={handlePress}>
            <Text>{user.name}</Text>
          </Pressable>
        );
      });
      
      // 2. Custom comparison function for complex props
      const ExpensiveComponent = memo(
        function ExpensiveComponent({ data, config }: Props) {
          return <View>{/* ... */}</View>;
        },
        (prevProps, nextProps) => {
          return (
            prevProps.data.id === nextProps.data.id &&
            prevProps.config.mode === nextProps.config.mode
          );
        }
      );
      
      // 3. useMemo for expensive computations
      function DataProcessor({ items, filters }: { items: Item[]; filters: Filters }) {
        const processedData = useMemo(() => {
          return items
            .filter((item) => {
              if (filters.category && item.category !== filters.category) return false;
              if (filters.minPrice && item.price < filters.minPrice) return false;
              if (filters.maxPrice && item.price > filters.maxPrice) return false;
              return true;
            })
            .sort((a, b) => {
              switch (filters.sortBy) {
                case "price-asc":
                  return a.price - b.price;
                case "price-desc":
                  return b.price - a.price;
                case "name":
                  return a.name.localeCompare(b.name);
                default:
                  return 0;
              }
            });
        }, [items, filters]);
      
        return <ItemList data={processedData} />;
      }
      
      // 4. Stable callbacks for memoized children
      function ParentWithStableCallbacks() {
        const [selectedId, setSelectedId] = useState<string | null>(null);
      
        const handleSelect = useCallback((id: string) => {
          setSelectedId(id);
        }, []);
      
        const handleAction = useCallback(
          (action: string) => {
            if (selectedId) {
              performAction(selectedId, action);
            }
          },
          [selectedId]
        );
      
        return (
          <>
            <MemoizedList onSelect={handleSelect} />
            <MemoizedActions onAction={handleAction} />
          </>
        );
      }
      ```
      
      ---
      
      ## Pattern 4: Avoiding Common Performance Issues
      
      ```typescript
      // BAD: Inline style objects create new reference every render
      function BadComponent() {
        return (
          <View style={{ padding: 16, margin: 8 }}>
            <Text style={{ fontSize: 16, color: "#000" }}>Hello</Text>
          </View>
        );
      }
      
      // GOOD: Use StyleSheet.create
      const styles = StyleSheet.create({
        container: { padding: 16, margin: 8 },
        text: { fontSize: 16, color: "#000" },
      });
      
      function GoodComponent() {
        return (
          <View style={styles.container}>
            <Text style={styles.text}>Hello</Text>
          </View>
        );
      }
      
      // BAD: Inline function in FlatList
      <FlatList
        data={items}
        renderItem={({ item }) => <ItemCard item={item} onPress={() => handlePress(item.id)} />}
      />
      
      // GOOD: Stable callbacks
      function GoodList({ items }: { items: Item[] }) {
        const handlePress = useCallback((id: string) => {
          // handle press
        }, []);
      
        const renderItem = useCallback(
          ({ item }: { item: Item }) => <MemoizedItemCard item={item} onPress={handlePress} />,
          [handlePress]
        );
      
        return <FlatList data={items} renderItem={renderItem} />;
      }
      
      // BAD: New array reference for extraData
      <FlatList
        data={items}
        extraData={[selectedId, sortOrder]} // New array every render
      />
      
      // GOOD: Memoized extraData
      function GoodListWithExtraData({ items, selectedId, sortOrder }: Props) {
        const extraData = useMemo(() => ({ selectedId, sortOrder }), [selectedId, sortOrder]);
      
        return <FlatList data={items} extraData={extraData} />;
      }
      ```
      
      ---
      
      ## Pattern 5: SectionList Optimization
      
      ```typescript
      import { memo, useCallback } from "react";
      import { SectionList, Text, View, StyleSheet } from "react-native";
      
      const SECTION_HEADER_HEIGHT = 40;
      const ITEM_HEIGHT = 60;
      
      interface Section {
        title: string;
        data: Item[];
      }
      
      const SectionHeader = memo(function SectionHeader({ title }: { title: string }) {
        return (
          <View style={styles.sectionHeader}>
            <Text style={styles.sectionTitle}>{title}</Text>
          </View>
        );
      });
      
      const SectionItem = memo(function SectionItem({
        item,
        onPress,
      }: {
        item: Item;
        onPress: (id: string) => void;
      }) {
        const handlePress = useCallback(() => onPress(item.id), [item.id, onPress]);
      
        return (
          <Pressable onPress={handlePress} style={styles.item}>
            <Text>{item.name}</Text>
          </Pressable>
        );
      });
      
      export function OptimizedSectionList({ sections, onItemPress }: {
        sections: Section[];
        onItemPress: (id: string) => void;
      }) {
        const renderSectionHeader = useCallback(
          ({ section }: { section: Section }) => <SectionHeader title={section.title} />,
          []
        );
      
        const renderItem = useCallback(
          ({ item }: { item: Item }) => <SectionItem item={item} onPress={onItemPress} />,
          [onItemPress]
        );
      
        const keyExtractor = useCallback((item: Item) => item.id, []);
      
        return (
          <SectionList
            sections={sections}
            renderSectionHeader={renderSectionHeader}
            renderItem={renderItem}
            keyExtractor={keyExtractor}
            stickySectionHeadersEnabled
            initialNumToRender={15}
            maxToRenderPerBatch={10}
            windowSize={5}
          />
        );
      }
      
      const styles = StyleSheet.create({
        sectionHeader: {
          height: SECTION_HEADER_HEIGHT,
          backgroundColor: "#F2F2F7",
          justifyContent: "center",
          paddingHorizontal: 16,
        },
        sectionTitle: {
          fontSize: 14,
          fontWeight: "600",
          color: "#8E8E93",
          textTransform: "uppercase",
        },
        item: {
          height: ITEM_HEIGHT,
          justifyContent: "center",
          paddingHorizontal: 16,
          backgroundColor: "#FFFFFF",
          borderBottomWidth: StyleSheet.hairlineWidth,
          borderBottomColor: "#E5E5EA",
        },
      });
      ```
      
      ---
      
      ## Pattern 6: Lazy Loading Screens
      
      ```typescript
      import { lazy, Suspense } from "react";
      import { ActivityIndicator, View, StyleSheet } from "react-native";
      
      // Lazy load heavy screens
      const HeavyDashboard = lazy(() => import("./screens/heavy-dashboard"));
      const AnalyticsScreen = lazy(() => import("./screens/analytics"));
      
      // Loading fallback component
      function ScreenLoader() {
        return (
          <View style={styles.loader}>
            <ActivityIndicator size="large" color="#007AFF" />
          </View>
        );
      }
      
      // Wrapper for lazy screens
      function LazyScreen({ component: Component, ...props }: { component: React.LazyExoticComponent<any> }) {
        return (
          <Suspense fallback={<ScreenLoader />}>
            <Component {...props} />
          </Suspense>
        );
      }
      
      // In navigator
      function MainNavigator() {
        return (
          <Stack.Navigator>
            <Stack.Screen name="Home" component={HomeScreen} />
            <Stack.Screen name="Dashboard">
              {(props) => <LazyScreen component={HeavyDashboard} {...props} />}
            </Stack.Screen>
            <Stack.Screen name="Analytics">
              {(props) => <LazyScreen component={AnalyticsScreen} {...props} />}
            </Stack.Screen>
          </Stack.Navigator>
        );
      }
      
      const styles = StyleSheet.create({
        loader: {
          flex: 1,
          justifyContent: "center",
          alignItems: "center",
        },
      });
      ```
      
      ---
      
      ## Pattern 7: Performance Monitoring Hook
      
      ```typescript
      import { useEffect, useRef } from "react";
      
      const RENDER_THRESHOLD = 10;
      const TIME_THRESHOLD_MS = 16; // 60fps = 16ms per frame
      
      export function usePerformanceMonitor(componentName: string) {
        const renderCount = useRef(0);
        const lastRenderTime = useRef(Date.now());
      
        useEffect(() => {
          renderCount.current += 1;
          const now = Date.now();
          const timeSinceLastRender = now - lastRenderTime.current;
          lastRenderTime.current = now;
      
          if (__DEV__) {
            if (renderCount.current > RENDER_THRESHOLD) {
              console.warn(
                `[Performance] ${componentName} has rendered ${renderCount.current} times`
              );
            }
      
            if (timeSinceLastRender < TIME_THRESHOLD_MS && renderCount.current > 1) {
              console.warn(
                `[Performance] ${componentName} rendered twice within ${timeSinceLastRender}ms`
              );
            }
          }
        });
      
        useEffect(() => {
          return () => {
            if (__DEV__ && renderCount.current > RENDER_THRESHOLD) {
              console.log(
                `[Performance] ${componentName} total renders: ${renderCount.current}`
              );
            }
          };
        }, [componentName]);
      }
      
      // Usage
      function ExpensiveComponent() {
        usePerformanceMonitor("ExpensiveComponent");
      
        return <View>{/* ... */}</View>;
      }
      ```
      
    • styling.md 11.8 KB
      # React Native - Styling Patterns
      
      > StyleSheet patterns, design tokens, platform-specific styling, and theming. See [core.md](core.md) for component architecture.
      
      **Prerequisites**: Understand [Pattern 3: Platform-Specific Code](../SKILL.md) from SKILL.md.
      
      ---
      
      ## Pattern 1: Design Tokens
      
      ```typescript
      // constants/design-tokens.ts
      
      // Spacing scale (4px base)
      export const SPACING = {
        xs: 4,
        sm: 8,
        md: 16,
        lg: 24,
        xl: 32,
        xxl: 48,
      } as const;
      
      // Typography
      export const FONT_SIZE = {
        xs: 12,
        sm: 14,
        md: 16,
        lg: 18,
        xl: 24,
        xxl: 32,
        xxxl: 40,
      } as const;
      
      export const FONT_WEIGHT = {
        regular: "400" as const,
        medium: "500" as const,
        semibold: "600" as const,
        bold: "700" as const,
      };
      
      // Colors - Light theme
      export const COLORS = {
        // Brand
        primary: "#007AFF",
        primaryLight: "#4DA3FF",
        primaryDark: "#0056B3",
        secondary: "#5856D6",
      
        // Semantic
        success: "#34C759",
        warning: "#FF9500",
        error: "#FF3B30",
        info: "#5AC8FA",
      
        // Neutral
        background: "#FFFFFF",
        surface: "#F2F2F7",
        surfaceElevated: "#FFFFFF",
      
        // Text
        text: "#000000",
        textSecondary: "#3C3C43",
        textTertiary: "#8E8E93",
        textInverse: "#FFFFFF",
      
        // Border
        border: "#C6C6C8",
        borderLight: "#E5E5EA",
      
        // Transparent
        overlay: "rgba(0,0,0,0.4)",
        transparent: "transparent",
      } as const;
      
      // Border radius
      export const RADIUS = {
        sm: 4,
        md: 8,
        lg: 12,
        xl: 16,
        full: 9999,
      } as const;
      
      // Shadows (iOS only - use ELEVATIONS for Android)
      export const SHADOWS = {
        sm: {
          shadowColor: "#000",
          shadowOffset: { width: 0, height: 1 },
          shadowOpacity: 0.05,
          shadowRadius: 2,
        },
        md: {
          shadowColor: "#000",
          shadowOffset: { width: 0, height: 2 },
          shadowOpacity: 0.1,
          shadowRadius: 4,
        },
        lg: {
          shadowColor: "#000",
          shadowOffset: { width: 0, height: 4 },
          shadowOpacity: 0.15,
          shadowRadius: 8,
        },
      } as const;
      
      // Elevations (Android only - use SHADOWS for iOS)
      export const ELEVATIONS = {
        sm: 2,
        md: 4,
        lg: 8,
      } as const;
      ```
      
      ---
      
      ## Pattern 2: StyleSheet with Design Tokens
      
      ```typescript
      import {
        StyleSheet,
        Platform,
        type ViewStyle,
        type TextStyle,
      } from "react-native";
      import {
        COLORS,
        SPACING,
        FONT_SIZE,
        RADIUS,
        SHADOWS,
        ELEVATIONS,
      } from "../constants/design-tokens";
      
      // Type-safe styles interface
      interface CardStyles {
        container: ViewStyle;
        header: ViewStyle;
        title: TextStyle;
        description: TextStyle;
        footer: ViewStyle;
      }
      
      export const cardStyles = StyleSheet.create<CardStyles>({
        container: {
          backgroundColor: COLORS.surfaceElevated,
          borderRadius: RADIUS.lg,
          padding: SPACING.md,
          ...Platform.select({
            ios: SHADOWS.md,
            android: { elevation: ELEVATIONS.md },
          }),
        },
        header: {
          flexDirection: "row",
          alignItems: "center",
          marginBottom: SPACING.sm,
        },
        title: {
          fontSize: FONT_SIZE.lg,
          fontWeight: "600",
          color: COLORS.text,
        },
        description: {
          fontSize: FONT_SIZE.md,
          color: COLORS.textSecondary,
          lineHeight: FONT_SIZE.md * 1.5,
        },
        footer: {
          marginTop: SPACING.md,
          paddingTop: SPACING.sm,
          borderTopWidth: StyleSheet.hairlineWidth,
          borderTopColor: COLORS.borderLight,
          flexDirection: "row",
          justifyContent: "flex-end",
        },
      });
      ```
      
      ---
      
      ## Pattern 3: New Architecture Style Props (React Native 0.76+)
      
      The New Architecture introduces `boxShadow` and `filter` props that work cross-platform.
      
      ```typescript
      import { View, StyleSheet } from "react-native";
      
      const styles = StyleSheet.create({
        // boxShadow - works on BOTH iOS and Android (New Architecture only)
        cardWithBoxShadow: {
          backgroundColor: "#FFFFFF",
          borderRadius: 12,
          padding: 16,
          // String syntax (CSS-like)
          boxShadow: "5 5 5 0 rgba(0, 0, 0, 0.2)",
          // OR object syntax:
          // boxShadow: {
          //   offsetX: 5,
          //   offsetY: 5,
          //   blurRadius: 5,
          //   spreadDistance: 0,
          //   color: "rgba(0, 0, 0, 0.2)",
          // },
        },
      
        // Inset shadow (New Architecture only, Android 10+)
        insetShadowCard: {
          boxShadow: "inset 0 2 4 0 rgba(0, 0, 0, 0.1)",
        },
      
        // filter - apply visual effects (New Architecture only)
        blurredOverlay: {
          filter: "blur(10)",
        },
      
        grayscaleImage: {
          filter: "grayscale(1)",
        },
      
        // Multiple filters
        combinedFilters: {
          filter: "saturate(0.5) brightness(1.2)",
          // OR array syntax:
          // filter: [{ saturate: 0.5 }, { brightness: 1.2 }],
        },
      });
      
      // NOTE: filter implies overflow: hidden and clips children outside parent bounds
      // NOTE: iOS filter only supports brightness and opacity
      // NOTE: Android blur and dropShadow require Android 12+
      ```
      
      **Why use boxShadow over legacy shadow props:**
      
      - Works on both iOS AND Android (legacy shadow props are iOS-only)
      - Supports inset shadows
      - Includes spread parameter
      - Works on Views without background color
      - Web-aligned syntax
      
      ---
      
      ## Pattern 4: Platform-Specific Styling
      
      ```typescript
      import { StyleSheet, Platform } from "react-native";
      import { COLORS, SHADOWS, ELEVATIONS, FONT_WEIGHT } from "../constants/design-tokens";
      
      const styles = StyleSheet.create({
        card: {
          backgroundColor: COLORS.surfaceElevated,
          borderRadius: 12,
          padding: 16,
          // Platform-specific shadows
          ...Platform.select({
            ios: {
              ...SHADOWS.md,
            },
            android: {
              elevation: ELEVATIONS.md,
            },
          }),
        },
      
        text: {
          // Platform-specific fonts
          fontFamily: Platform.select({
            ios: "San Francisco",
            android: "Roboto",
            default: "System",
          }),
          // Android only reliably supports normal/bold
          fontWeight: Platform.select({
            ios: FONT_WEIGHT.semibold,
            android: FONT_WEIGHT.bold,
          }),
        },
      
        button: {
          // Platform-specific hit slop
          ...Platform.select({
            ios: {
              paddingVertical: 12,
            },
            android: {
              paddingVertical: 14, // Slightly larger for Android touch targets
            },
          }),
        },
      });
      
      // Platform-specific component props
      function PlatformButton({ onPress, children }: ButtonProps) {
        return (
          <Pressable
            onPress={onPress}
            style={styles.button}
            android_ripple={Platform.OS === "android" ? { color: "rgba(0,0,0,0.1)" } : undefined}
          >
            {children}
          </Pressable>
        );
      }
      ```
      
      ---
      
      ## Pattern 5: Theming with Context
      
      ```typescript
      // context/theme-context.tsx
      import { createContext, useContext, useState, useMemo, type ReactNode } from "react";
      import { useColorScheme } from "react-native";
      
      // Theme types
      interface Theme {
        colors: {
          primary: string;
          background: string;
          surface: string;
          text: string;
          textSecondary: string;
          border: string;
        };
        spacing: typeof SPACING;
        radius: typeof RADIUS;
      }
      
      const lightTheme: Theme = {
        colors: {
          primary: "#007AFF",
          background: "#FFFFFF",
          surface: "#F2F2F7",
          text: "#000000",
          textSecondary: "#3C3C43",
          border: "#C6C6C8",
        },
        spacing: SPACING,
        radius: RADIUS,
      };
      
      const darkTheme: Theme = {
        colors: {
          primary: "#0A84FF",
          background: "#000000",
          surface: "#1C1C1E",
          text: "#FFFFFF",
          textSecondary: "#EBEBF5",
          border: "#38383A",
        },
        spacing: SPACING,
        radius: RADIUS,
      };
      
      // Context
      interface ThemeContextValue {
        theme: Theme;
        isDark: boolean;
        toggleTheme: () => void;
        setTheme: (mode: "light" | "dark" | "system") => void;
      }
      
      const ThemeContext = createContext<ThemeContextValue | null>(null);
      
      // Provider
      export function ThemeProvider({ children }: { children: ReactNode }) {
        const systemColorScheme = useColorScheme();
        const [mode, setMode] = useState<"light" | "dark" | "system">("system");
      
        const isDark = useMemo(() => {
          if (mode === "system") {
            return systemColorScheme === "dark";
          }
          return mode === "dark";
        }, [mode, systemColorScheme]);
      
        const theme = isDark ? darkTheme : lightTheme;
      
        const toggleTheme = () => {
          setMode((current) => (current === "dark" ? "light" : "dark"));
        };
      
        const value = useMemo(
          () => ({ theme, isDark, toggleTheme, setTheme: setMode }),
          [theme, isDark]
        );
      
        return (
          <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
        );
      }
      
      // Hook
      export function useTheme() {
        const context = useContext(ThemeContext);
        if (!context) {
          throw new Error("useTheme must be used within ThemeProvider");
        }
        return context;
      }
      
      // Styled component using theme
      function ThemedCard({ children }: { children: ReactNode }) {
        const { theme } = useTheme();
      
        return (
          <View
            style={{
              backgroundColor: theme.colors.surface,
              borderRadius: theme.radius.lg,
              padding: theme.spacing.md,
              borderWidth: 1,
              borderColor: theme.colors.border,
            }}
          >
            {children}
          </View>
        );
      }
      ```
      
      ---
      
      ## Pattern 6: Dynamic Style Arrays with Variants
      
      ```typescript
      import { StyleSheet, View, Text, type ViewStyle, type TextStyle } from "react-native";
      import { useMemo } from "react";
      
      interface BadgeProps {
        children: string;
        variant?: "default" | "success" | "warning" | "error";
        size?: "sm" | "md" | "lg";
        outlined?: boolean;
        style?: ViewStyle;
      }
      
      const BADGE_COLORS = {
        default: { bg: "#E5E5EA", text: "#3C3C43" },
        success: { bg: "#D1FAE5", text: "#065F46" },
        warning: { bg: "#FEF3C7", text: "#92400E" },
        error: { bg: "#FEE2E2", text: "#991B1B" },
      } as const;
      
      const BADGE_SIZES = {
        sm: { paddingVertical: 2, paddingHorizontal: 6, fontSize: 10 },
        md: { paddingVertical: 4, paddingHorizontal: 8, fontSize: 12 },
        lg: { paddingVertical: 6, paddingHorizontal: 12, fontSize: 14 },
      } as const;
      
      export function Badge({
        children,
        variant = "default",
        size = "md",
        outlined = false,
        style,
      }: BadgeProps) {
        const containerStyle = useMemo<ViewStyle[]>(
          () => [
            styles.base,
            {
              paddingVertical: BADGE_SIZES[size].paddingVertical,
              paddingHorizontal: BADGE_SIZES[size].paddingHorizontal,
              backgroundColor: outlined ? "transparent" : BADGE_COLORS[variant].bg,
              borderWidth: outlined ? 1 : 0,
              borderColor: BADGE_COLORS[variant].bg,
            },
            style,
          ].filter(Boolean) as ViewStyle[],
          [variant, size, outlined, style]
        );
      
        const textStyle = useMemo<TextStyle[]>(
          () => [
            styles.text,
            {
              fontSize: BADGE_SIZES[size].fontSize,
              color: BADGE_COLORS[variant].text,
            },
          ],
          [variant, size]
        );
      
        return (
          <View style={containerStyle}>
            <Text style={textStyle}>{children}</Text>
          </View>
        );
      }
      
      const styles = StyleSheet.create({
        base: {
          borderRadius: 9999,
          alignSelf: "flex-start",
        },
        text: {
          fontWeight: "600",
          textTransform: "uppercase",
        },
      });
      ```
      
      ---
      
      ## Pattern 7: Responsive Styling
      
      ```typescript
      import { useWindowDimensions } from "react-native";
      
      // Constants for breakpoints
      const BREAKPOINTS = {
        sm: 640,
        md: 768,
        lg: 1024,
        xl: 1280,
      } as const;
      
      // Hook for responsive values
      function useResponsiveValue<T>(values: { default: T; sm?: T; md?: T; lg?: T; xl?: T }): T {
        const { width } = useWindowDimensions();
      
        if (width >= BREAKPOINTS.xl && values.xl !== undefined) return values.xl;
        if (width >= BREAKPOINTS.lg && values.lg !== undefined) return values.lg;
        if (width >= BREAKPOINTS.md && values.md !== undefined) return values.md;
        if (width >= BREAKPOINTS.sm && values.sm !== undefined) return values.sm;
      
        return values.default;
      }
      
      // Usage
      function ResponsiveGrid({ items }: { items: Item[] }) {
        const numColumns = useResponsiveValue({
          default: 2,
          sm: 2,
          md: 3,
          lg: 4,
          xl: 5,
        });
      
        const itemPadding = useResponsiveValue({
          default: 8,
          md: 16,
          lg: 24,
        });
      
        return (
          <FlatList
            data={items}
            numColumns={numColumns}
            key={numColumns} // Re-render when columns change
            contentContainerStyle={{ padding: itemPadding }}
            renderItem={({ item }) => (
              <View style={{ flex: 1 / numColumns, padding: itemPadding / 2 }}>
                <ItemCard item={item} />
              </View>
            )}
          />
        );
      }
      ```
      
  • reference.md 8.9 KB
    # React Native Reference
    
    > Decision frameworks, checklists, and quick reference. See [SKILL.md](SKILL.md) for red flags and anti-patterns.
    
    ---
    
    ## Decision Framework
    
    ### New Architecture (React Native 0.76+)
    
    ```
    Is your app on React Native 0.76+?
    ├─ YES → New Architecture is ENABLED by default
    │   └─ Check library compatibility (90%+ of popular libs support it)
    └─ NO → Consider upgrading (legacy architecture is deprecated with warnings)
    
    Need to opt out temporarily?
    ├─ Add to app.json (Expo): newArchEnabled: false
    ├─ Add to gradle.properties (bare): newArchEnabled=false
    └─ WARNING: App will crash in 0.78+ if legacy architecture is forced
    
    Are you using Reanimated?
    ├─ Reanimated 4.x → New Architecture ONLY (requires react-native-worklets)
    └─ Reanimated 3.x → Supports both architectures (still maintained)
    ```
    
    ### Expo vs Bare Workflow
    
    ```
    Starting a new React Native project?
    ├─ Need custom native modules (not in Expo SDK)?
    │   ├─ YES → Bare workflow or Expo with development builds
    │   └─ NO → Continue...
    ├─ App size critical (<15MB)?
    │   ├─ YES → Bare workflow (Expo adds overhead)
    │   └─ NO → Continue...
    ├─ Team has native (iOS/Android) expertise?
    │   ├─ YES → Either (lean bare if complex native work)
    │   └─ NO → Expo (handles native complexity)
    ├─ Need OTA updates?
    │   ├─ YES → Expo (EAS Update)
    │   └─ NO → Either
    └─ Default → Expo (95% of cases)
    ```
    
    ### List Component Choice
    
    ```
    How many items in the list?
    ├─ < 10 items → ScrollView + map() is fine
    ├─ 10-50 items → FlashList or FlatList recommended
    ├─ 50+ items → FlashList STRONGLY recommended (or FlatList REQUIRED)
    └─ 1000+ items → FlashList v2 (best performance)
    
    Are you on New Architecture (0.76+)?
    ├─ YES → FlashList v2 (auto-sizes items, 50% less blank area)
    │   └─ No estimatedItemSize needed (measures real items)
    └─ NO → FlashList v1 or FlatList
        └─ estimatedItemSize REQUIRED for FlashList v1
    
    Do items have variable heights?
    ├─ FlashList v2 → Handles automatically, items can resize dynamically
    ├─ FlashList v1 → Provide estimatedItemSize or overrideItemLayout
    └─ FlatList → Cannot use getItemLayout (performance hit)
    
    Need sections with headers?
    ├─ YES → SectionList (or FlashList with getItemType)
    └─ NO → FlashList or FlatList
    
    FlashList v2 vs FlatList Decision:
    ├─ Complex items, low-end Android → FlashList v2 (cell recycling)
    ├─ Simple items, high-end devices → Either works
    ├─ Need masonry layout → FlashList v2 (built-in support)
    ├─ Need maintainVisibleContentPosition → FlashList v2 (enabled by default)
    └─ Default recommendation → FlashList v2 (better performance)
    ```
    
    ### Styling Approach
    
    ```
    Does component have variants (primary/secondary, sm/md/lg)?
    ├─ YES → StyleSheet with computed styles or style arrays
    │   OR → Use your variant styling solution
    └─ NO → StyleSheet.create for static styles
    
    Are values dynamic (runtime values like theme colors)?
    ├─ YES → Inline styles or style arrays
    └─ NO → StyleSheet.create (better performance)
    ```
    
    ### Navigation Pattern
    
    ```
    What type of navigation flow?
    ├─ Linear flow (onboarding, checkout) → Stack Navigator
    ├─ Main app sections → Tab Navigator (bottom tabs)
    ├─ Settings/menu → Drawer Navigator
    ├─ Modals → Stack with presentation: 'modal'
    └─ Deep linking required → Configure linking config
    
    React Navigation 7+ API Choice:
    ├─ Simple app, TypeScript-first → Static API (less boilerplate)
    ├─ Complex dynamic navigation → Dynamic API (more flexible)
    ├─ Mix of both → Use static for top-level, dynamic for nested
    └─ File-based routing → Use your managed workflow's router (built on React Navigation)
    
    Auth flow pattern?
    ├─ Switch between auth/main navigators based on auth state
    └─ Don't conditionally render screens in same navigator
    ```
    
    ### Memoization Decision
    
    ```
    Should I memoize this?
    ├─ Is it a FlatList renderItem callback?
    │   └─ YES → Always useCallback
    ├─ Is it a component receiving stable props?
    │   └─ YES → Consider React.memo
    ├─ Is computation expensive (>5ms)?
    │   └─ YES → useMemo
    ├─ Is it a callback passed to memoized child?
    │   └─ YES → useCallback
    └─ Default → Don't memoize (premature optimization)
    ```
    
    ---
    
    ## File Organization Reference
    
    ### Recommended Directory Structure
    
    ```
    src/
    ├── app/                    # File-based routes (if using managed workflow)
    ├── screens/                # Screen components
    │   ├── home/
    │   │   ├── home-screen.tsx
    │   │   └── components/     # Screen-specific components
    │   └── auth/
    │       ├── login-screen.tsx
    │       └── register-screen.tsx
    ├── components/             # Shared components
    │   ├── ui/                 # Base UI components
    │   │   ├── button.tsx
    │   │   ├── input.tsx
    │   │   └── card.tsx
    │   └── features/           # Feature-specific components
    ├── navigation/             # Navigation configuration
    │   ├── root-navigator.tsx
    │   ├── auth-navigator.tsx
    │   └── types.ts            # Navigation type definitions
    ├── hooks/                  # Custom hooks
    ├── stores/                 # State management stores
    ├── services/               # API services
    ├── utils/                  # Utility functions
    ├── constants/              # App constants, design tokens
    └── types/                  # Shared TypeScript types
    ```
    
    ### File Naming Conventions
    
    ```
    Components:       kebab-case.tsx        (e.g., user-profile.tsx)
    Screens:          kebab-case-screen.tsx (e.g., home-screen.tsx)
    Hooks:            use-kebab-case.ts     (e.g., use-auth.ts)
    Stores:           kebab-case-store.ts   (e.g., auth-store.ts)
    Utils:            kebab-case.ts         (e.g., format-date.ts)
    Types:            types.ts or kebab-case.types.ts
    Platform files:   name.ios.tsx, name.android.tsx
    ```
    
    ---
    
    ## Performance Checklist
    
    ### FlatList Optimization
    
    - [ ] Using FlatList (not ScrollView + map) for 20+ items
    - [ ] renderItem wrapped in useCallback
    - [ ] keyExtractor returns stable unique ID (not index)
    - [ ] getItemLayout provided if items have fixed height
    - [ ] initialNumToRender, maxToRenderPerBatch, windowSize tuned
    - [ ] removeClippedSubviews={true} on Android for memory
    - [ ] Item components wrapped in React.memo
    
    ### Component Optimization
    
    - [ ] No inline styles for static values (use StyleSheet)
    - [ ] No inline functions passed to memoized children
    - [ ] useMemo for expensive computations (>5ms)
    - [ ] useCallback for callbacks passed to children
    - [ ] React.memo on frequently re-rendering pure components
    
    ### Image Optimization
    
    - [ ] Using an optimized image library for heavy image usage
    - [ ] Images sized appropriately (not 4K images in thumbnails)
    - [ ] Preloading critical images
    - [ ] Using appropriate resizeMode
    - [ ] Caching enabled for network images
    
    ### Navigation Optimization
    
    - [ ] Lazy loading heavy screens
    - [ ] Screen preloading for likely next screens
    - [ ] useFocusEffect for resource setup/cleanup
    - [ ] Avoiding inline component functions in Screen definitions
    
    ---
    
    ## Quick Reference
    
    ### Essential Imports
    
    ```typescript
    // Core React Native
    import {
      View,
      Text,
      Pressable,
      FlatList,
      StyleSheet,
      Platform,
      Dimensions,
      KeyboardAvoidingView,
      ScrollView,
      Image,
      TextInput,
      ActivityIndicator,
    } from "react-native";
    
    // Safe Area
    import {
      SafeAreaView,
      useSafeAreaInsets,
    } from "react-native-safe-area-context";
    
    // Navigation
    import {
      useNavigation,
      useRoute,
      useFocusEffect,
    } from "@react-navigation/native";
    import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
    
    // Animations
    import Animated, {
      useSharedValue,
      useAnimatedStyle,
      withSpring,
      withTiming,
    } from "react-native-reanimated";
    import { Gesture, GestureDetector } from "react-native-gesture-handler";
    ```
    
    ### Common TypeScript Patterns
    
    ```typescript
    // Navigation types
    type RootStackParamList = {
      Home: undefined;
      Profile: { userId: string };
      Settings: { section?: string };
    };
    
    type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
    
    // Component props
    interface ComponentProps {
      children: React.ReactNode;
      style?: ViewStyle;
      testID?: string;
    }
    
    // FlatList renderItem
    type RenderItem<T> = ({
      item,
      index,
    }: {
      item: T;
      index: number;
    }) => React.ReactElement;
    ```
    
    ### CLI Commands
    
    ```bash
    # React Native CLI (bare workflow)
    npx react-native start            # Start Metro bundler
    npx react-native run-ios          # Run on iOS simulator
    npx react-native run-android      # Run on Android emulator
    cd ios && pod install             # Install iOS dependencies
    ```
    
    > For managed workflow CLI commands (start, build, update), see your managed workflow's skill documentation.
    
  • SKILL.md 12.9 KB
    ---
    name: mobile-framework-react-native
    description: React Native mobile development patterns - New Architecture (Fabric, TurboModules, JSI), component architecture, React Navigation 7+, FlashList v2 optimization, gestures with Reanimated 4, platform-specific code, React 19 features
    ---
    
    # React Native Development Patterns
    
    > **Quick Guide:** Build cross-platform mobile apps with React Native's New Architecture (default since 0.76). Use FlashList for performant lists (or FlatList with proper optimization). Use type-safe navigation hooks with static or dynamic API. Keep components small, memoize callbacks passed to lists, and test on both platforms from day one.
    
    ---
    
    <critical_requirements>
    
    ## CRITICAL: Before Using This Skill
    
    > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
    
    **(You MUST use FlashList (preferred) or FlatList for lists with more than 20 items - NEVER ScrollView with .map() for long lists)**
    
    **(You MUST memoize renderItem callbacks and use stable keyExtractor functions - avoid key props on FlashList items as it breaks recycling)**
    
    **(You MUST use react-native-safe-area-context for safe areas - React Native's built-in SafeAreaView is deprecated in 0.81+ and will be removed)**
    
    **(You MUST test on BOTH iOS AND Android from day one - platform differences cause bugs)**
    
    **(You MUST use Platform.select() or platform-specific files for platform differences - shadows, fonts, and feedback differ)**
    
    **(You MUST be aware the New Architecture is enabled by default since React Native 0.76 - Fabric, TurboModules, and bridgeless mode)**
    
    </critical_requirements>
    
    ---
    
    **Auto-detection:** React Native, react-native, React Navigation, @react-navigation, StyleSheet, FlatList, FlashList, ScrollView, View, Text, Pressable, TouchableOpacity, Platform.OS, Platform.select, SafeAreaView, KeyboardAvoidingView, Reanimated, Gesture Handler, TurboModules, Fabric, JSI, New Architecture
    
    **When to use:**
    
    - Building cross-platform iOS and Android mobile applications
    - Creating native mobile UIs with React patterns
    - Implementing mobile navigation with stack, tab, or drawer patterns
    - Optimizing list performance with FlashList/FlatList and virtualization
    - Adding gestures and animations with Reanimated 4
    - Handling platform-specific code for iOS vs Android differences
    - Working with React Native's New Architecture (Fabric, TurboModules, JSI)
    
    **Key patterns covered:**
    
    - New Architecture fundamentals (Fabric, TurboModules, JSI, bridgeless mode)
    - Component architecture with accessibility props and platform-specific patterns
    - React Navigation 7+ with type-safe hooks, static API, and auth flows
    - FlashList/FlatList optimization with memoization and cell recycling
    - Platform-specific code with Platform.select and file extensions
    - Safe area and keyboard handling
    - React 19 features (React Native 0.78+): useOptimistic, `use`, ref as props
    
    **When NOT to use:**
    
    - Web-only React applications (use standard React patterns)
    - React Native Web hybrid apps (requires additional considerations)
    - Flutter, Swift, or Kotlin native development
    
    **Detailed Resources:**
    
    - [examples/core.md](examples/core.md) - Component architecture, compound components
    - [examples/navigation.md](examples/navigation.md) - Type-safe navigation, auth flows, deep linking
    - [examples/styling.md](examples/styling.md) - StyleSheet, design tokens, theming, responsive styling
    - [examples/performance.md](examples/performance.md) - FlashList, FlatList optimization, memoization
    - [reference.md](reference.md) - Decision frameworks, checklists, CLI commands
    
    ---
    
    <philosophy>
    
    ## Philosophy
    
    React Native enables building native mobile apps using React patterns. The key insight is that **mobile has different constraints than web**: performance matters more, platform conventions differ, and users expect native feel.
    
    **Core principles:**
    
    1. **New Architecture first** - React Native 0.76+ uses the New Architecture by default (Fabric, TurboModules, JSI, bridgeless mode)
    2. **Platform-first thinking** - iOS and Android have different UX conventions (haptics, ripples, navigation patterns)
    3. **Performance by default** - Mobile devices are constrained; use FlashList/FlatList, memoize callbacks, avoid inline styles
    4. **Native feel matters** - Use native components, proper keyboard handling, safe area insets
    5. **Type safety prevents bugs** - Type navigation params, props, and native module interfaces
    6. **Test both platforms early** - Platform bugs compound over time; test daily on both
    
    **New Architecture (React Native 0.76+):**
    
    The New Architecture removes the legacy bridge and provides:
    
    - **Fabric** - Modern rendering engine with synchronous layout effects
    - **TurboModules** - Lazy-loaded native modules with type-safe interfaces
    - **JSI (JavaScript Interface)** - Direct synchronous JS-to-native calls without serialization
    - **Bridgeless Mode** - Complete removal of the async bridge for better performance
    
    Performance improvements with New Architecture: ~15ms faster app startup (~8% improvement), ~3.8MB smaller app size (20% reduction), ~15x faster Metro resolver, ~4x faster warm builds.
    
    **Mental model:**
    
    React Native is NOT "write once, run anywhere" - it's "learn once, write anywhere." Expect to write some platform-specific code. The shared codebase is typically 80-95%, not 100%.
    
    </philosophy>
    
    ---
    
    <patterns>
    
    ## Core Patterns
    
    ### Pattern 1: Component Architecture
    
    Build components with TypeScript, accessibility props, and platform awareness. Key concerns: use `Pressable` over `TouchableOpacity`, include `accessibilityRole`/`accessibilityState`, use `testID` for E2E tests, and memoize styles with `useMemo`.
    
    ```typescript
    // Key pattern: accessibility + testID + memoized styles
    <Pressable
      ref={ref}
      style={buttonStyle}
      onPress={handlePress}
      disabled={disabled}
      testID={testID}
      accessibilityRole="button"
      accessibilityState={{ disabled }}
    >
      <Text style={styles.text}>{children}</Text>
    </Pressable>
    ```
    
    **Why good:** accessibilityRole/State for screen readers, testID for E2E, Pressable supports android_ripple unlike TouchableOpacity
    
    See [examples/core.md](examples/core.md) for full component with forwardRef, variants, and loading state.
    
    ---
    
    ### Pattern 2: Safe Area and Keyboard Handling
    
    Handle device notches, status bars, and keyboard properly. Use `react-native-safe-area-context` (RN's built-in SafeAreaView is deprecated in 0.81+).
    
    ```typescript
    import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
    
    // Screen wrapper with safe areas
    <SafeAreaView style={{ flex: 1 }} edges={["top", "bottom"]}>
      {children}
    </SafeAreaView>
    
    // Keyboard handling for forms
    <KeyboardAvoidingView
      style={{ flex: 1 }}
      behavior={Platform.OS === "ios" ? "padding" : "height"}
      keyboardVerticalOffset={Platform.select({ ios: HEADER_HEIGHT, android: 0 })}
    >
      <ScrollView keyboardShouldPersistTaps="handled">
        <FormContent />
      </ScrollView>
    </KeyboardAvoidingView>
    ```
    
    **Why good:** SafeAreaView handles notches/Dynamic Island, KeyboardAvoidingView prevents keyboard overlap, Platform.select handles iOS/Android differences
    
    ---
    
    ### Pattern 3: Platform-Specific Code
    
    Handle iOS and Android differences with `Platform.select` for small differences, separate files (`.ios.tsx`/`.android.tsx`) for significant divergence.
    
    ```typescript
    // Platform.select for shadows (iOS shadow props ignored on Android)
    const styles = StyleSheet.create({
      card: {
        ...Platform.select({
          ios: {
            shadowColor: "#000",
            shadowOffset: { width: 0, height: 2 },
            shadowOpacity: 0.1,
            shadowRadius: 4,
          },
          android: { elevation: 4 },
        }),
      },
      text: {
        fontWeight: Platform.select({ ios: "600", android: "bold" }),
      },
    });
    ```
    
    **Why good:** iOS shadow props are completely ignored on Android, fontWeight 100-900 unreliable on Android
    
    For platform-specific file splitting with haptics, see [examples/core.md](examples/core.md).
    
    ---
    
    ### Pattern 4: FlashList and FlatList Optimization
    
    Use FlashList for performant lists (cell recycling, 50% less blank area). FlashList v2 is New Architecture only. Always memoize renderItem and keyExtractor. Never add `key` props to FlashList items (breaks recycling).
    
    ```typescript
    // Critical: memoized renderItem + no key prop on items
    const renderItem = useCallback(
      ({ item }: { item: Product }) => (
        <ProductItem item={item} onPress={onProductPress} />
      ),
      [onProductPress]
    );
    
    <FlashList
      data={products}
      renderItem={renderItem}
      estimatedItemSize={ITEM_HEIGHT} // Optional in v2, required in v1
      getItemType={(item) => item.category} // Improves recycling pool
    />
    ```
    
    **Why good:** useCallback prevents renderItem recreation, getItemType optimizes recycling, no key prop preserves FlashList's main benefit
    
    See [examples/performance.md](examples/performance.md) for full FlashList and FlatList optimization patterns.
    
    ---
    
    ### Pattern 5: React 19 Features (React Native 0.78+)
    
    React Native 0.78+ includes React 19 with new hooks and simplified patterns.
    
    ```typescript
    import { useOptimistic } from "react";
    
    // useOptimistic - optimistic UI updates with automatic rollback
    const [optimisticMessages, addOptimisticMessage] = useOptimistic(
      messages,
      (state, newMessage: Message) => [...state, { ...newMessage, sending: true }]
    );
    
    // ref as props - no more forwardRef wrapper needed (React 19)
    function Input({ ref, placeholder, onChangeText }: InputProps) {
      return <TextInput ref={ref} placeholder={placeholder} onChangeText={onChangeText} />;
    }
    ```
    
    **Why good:** useOptimistic provides automatic rollback on errors, ref as props eliminates forwardRef boilerplate
    
    </patterns>
    
    ---
    
    <red_flags>
    
    ## RED FLAGS
    
    **High Priority Issues:**
    
    - Using ScrollView + map() for lists with 50+ items - causes severe performance, use FlashList or FlatList
    - Adding key prop to FlashList items - BREAKS cell recycling, eliminates FlashList's main benefit
    - Using React Native's built-in SafeAreaView - deprecated in 0.81+, use react-native-safe-area-context
    - Not testing on both platforms - iOS/Android differences compound; test daily on both
    - Inline functions in FlatList/FlashList renderItem - creates new function every render, breaks memoization
    - Using Reanimated 4 with old architecture - Reanimated 4.x is New Architecture ONLY
    
    **Medium Priority Issues:**
    
    - Hardcoded colors/spacing instead of constants - breaks consistency, makes theming impossible
    - Not using Platform.select for shadows - iOS shadow props don't work on Android (use elevation)
    - Missing keyboard handling on forms - keyboard covers inputs without KeyboardAvoidingView
    - Using TouchableOpacity everywhere - Pressable is more flexible and supports android_ripple
    
    **Gotchas & Edge Cases:**
    
    - Android fontWeight only supports 'normal' and 'bold' reliably - 100-900 values may not work
    - iOS shadow props are completely ignored on Android - must use elevation for Android shadows
    - StatusBar backgroundColor only works on Android - iOS uses translucent status bar
    - FlatList onEndReached fires immediately if data fits screen - use onEndReachedThreshold carefully
    - KeyboardAvoidingView behavior differs: 'padding' for iOS, 'height' for Android
    - React Native doesn't have CSS cascade - each component must have complete styles
    - Text must be wrapped in `<Text>` component - raw strings cause crashes
    - New Architecture enabled by default in 0.76+ - some older libraries may need updates
    - FlashList v2 is New Architecture only - use v1 or FlatList if on old architecture
    - React Native 0.78+ uses React 19 - propTypes removed, forwardRef optional
    - React 19 adoption in recent SDK versions - check for breaking changes in your dependencies
    - Android 15/16 enforces edge-to-edge - must handle safe areas properly
    - Reanimated 4 requires react-native-worklets - Reanimated 3 will not work with it installed
    - boxShadow and filter props are New Architecture only - not available on legacy architecture
    - Reanimated 4: withSpring no longer uses restDisplacementThreshold/restSpeedThreshold - replaced by energyThreshold
    
    </red_flags>
    
    ---
    
    <critical_reminders>
    
    ## CRITICAL REMINDERS
    
    > **All code must follow project conventions in CLAUDE.md**
    
    **(You MUST use FlashList (preferred) or FlatList for lists with more than 20 items - NEVER ScrollView with .map() for long lists)**
    
    **(You MUST memoize renderItem callbacks and use stable keyExtractor functions - avoid key props on FlashList items as it breaks recycling)**
    
    **(You MUST use react-native-safe-area-context for safe areas - React Native's built-in SafeAreaView is deprecated in 0.81+ and will be removed)**
    
    **(You MUST test on BOTH iOS AND Android from day one - platform differences cause bugs)**
    
    **(You MUST use Platform.select() or platform-specific files for platform differences - shadows, fonts, and feedback differ)**
    
    **(You MUST be aware the New Architecture is enabled by default since React Native 0.76 - Fabric, TurboModules, and bridgeless mode)**
    
    **Failure to follow these rules will result in poor performance, platform-specific bugs, and broken UX on mobile devices.**
    
    </critical_reminders>
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related