·12 min read

AI Coding Rules for Mobile Development

Practical AI coding rules for React Native and mobile projects, covering navigation, platform-specific code, state management, and testing patterns.

AI Coding Rules for Mobile Development

Mobile development has its own set of patterns that generic AI rules miss entirely. React Native looks like React, but the component model, navigation, styling, and platform APIs are different enough that your AI assistant will generate broken or suboptimal code without specific guidance.

The problem is amplified by the gap between web and mobile. AI models are trained on far more web React code than React Native code. Without rules, they default to web patterns: CSS-in-JS with class names, react-router-dom for navigation, window.localStorage for persistence. None of these work on mobile.

This guide covers the rules you need for React Native projects and mobile development more broadly. Each section includes rules you can copy directly into your .cursorrules or CLAUDE.md file and adapt for your codebase.


Declaring your mobile stack

The first rule in any mobile project is an explicit stack declaration. AI tools need to know they are working in a mobile context, not a web one.

## Stack
- React Native 0.76 (New Architecture enabled)
- TypeScript 5 (strict mode)
- Expo SDK 52 (managed workflow)
- React Navigation v7 for routing
- Zustand for global state
- React Query (TanStack Query) for server state
- React Native Testing Library + Jest for tests
- EAS Build for CI/CD

This single block prevents the most common mistake: the AI treating your project like a web app. Specifying "React Native" and "Expo" tells the model to use mobile-appropriate APIs, components, and libraries.

If you are using a bare React Native project without Expo, say so explicitly. If you are using Flutter instead, swap in the equivalent declarations. The principle is the same: be precise about your runtime, framework version, and tooling.


Navigation is where mobile diverges most from web. React Navigation has a completely different API from any web router, and AI tools will default to web patterns unless you correct them.

## Navigation

This project uses React Navigation v7 with a typed navigation structure.

Stack layout:
- RootStack (native stack)
  - AuthStack (screens: Login, Register, ForgotPassword)
  - MainTabs (bottom tab navigator)
    - HomeStack
    - SearchStack
    - ProfileStack

Rules:
- Define all screen params in src/navigation/types.ts using RootStackParamList
- Always type navigation props: NativeStackScreenProps<RootStackParamList, "ScreenName">
- Use useNavigation() with typed hook: useNavigation<NavigationProp<RootStackParamList>>()
- Never use string-based navigation without type safety
- Deep linking config lives in src/navigation/linking.ts
- Do NOT use react-router or next/navigation - this is React Navigation only

The typed param list is worth calling out specifically. Without it, AI tools generate navigation calls like navigation.navigate("Profile", { userId: 123 }) with no type checking on the params. That compiles fine, but crashes at runtime when a screen expects different params.

Here is the kind of type file your rules should reference:

## Navigation types example

// src/navigation/types.ts
type RootStackParamList = {
  Auth: NavigatorScreenParams<AuthStackParamList>;
  Main: NavigatorScreenParams<MainTabParamList>;
};

type AuthStackParamList = {
  Login: undefined;
  Register: { referralCode?: string };
  ForgotPassword: { email?: string };
};

type MainTabParamList = {
  Home: undefined;
  Search: { query?: string };
  Profile: { userId: string };
};

Including a concrete example like this in your rules file makes the AI reproduce the exact pattern every time it creates a new screen or navigation action.


Platform-specific code

React Native runs on iOS and Android, and the two platforms have differences that your rules need to address. Without guidance, AI tools either ignore platform differences entirely or overuse Platform.OS checks in ways that create messy conditional logic.

## Platform-specific code

Use platform-specific file extensions for components with significant platform differences:
  component.ios.tsx
  component.android.tsx

For minor differences within a single file, use Platform.select():
  const padding = Platform.select({ ios: 16, android: 12 });

Rules:
- Use SafeAreaView (from react-native-safe-area-context, NOT from react-native) on all screen-level components
- Use KeyboardAvoidingView with behavior="padding" on iOS, behavior="height" on Android
- Handle Android back button explicitly with BackHandler or navigation listeners
- Use StatusBar component to control status bar appearance per screen
- Test on both platforms before marking a feature complete

A common AI mistake is importing SafeAreaView from react-native instead of react-native-safe-area-context. The built-in one is deprecated and only works on iOS. The community package works on both platforms. A one-line rule prevents this.

## Platform imports - do NOT use these
- Do NOT import SafeAreaView from "react-native" - use "react-native-safe-area-context"
- Do NOT import PushNotification directly - use "expo-notifications" (Expo) or "@react-native-firebase/messaging"
- Do NOT use AsyncStorage from "react-native" - use "@react-native-async-storage/async-storage"
- Do NOT use Alert.alert() for complex dialogs - use a modal component

These "do NOT import" rules catch the most frequent package confusion issues. AI models are trained on older React Native code where many of these imports were valid. Your rules need to override that training.


Styling on mobile

React Native styling looks like CSS but is not CSS. Flexbox is the default layout model, but the defaults are different (flex-direction is column, not row). There are no cascading styles, no class names, and no media queries.

## Styling

Use React Native StyleSheet for all component styles:
  const styles = StyleSheet.create({
    container: {
      flex: 1,
      padding: 16,
      backgroundColor: colors.background,
    },
  });

Rules:
- Use StyleSheet.create() for all styles - never inline style objects in JSX
- Flexbox is the only layout model - no CSS Grid, no floats
- Default flexDirection is "column" (not "row" like web CSS)
- Use design tokens from src/theme/tokens.ts for colors, spacing, and typography
- Do NOT use pixel units with "px" suffix - all values are density-independent pixels (plain numbers)
- Do NOT use percentage-based widths for responsive layout - use flex ratios
- For responsive sizing, use useWindowDimensions() hook, not Dimensions.get() (which does not update on rotation)

The "no px suffix" rule catches a web habit that AI tools bring into mobile code. In React Native, padding: 16 is correct. padding: "16px" throws an error. Simple rule, but it saves time.

Design tokens

Just like in web projects, centralizing your design values matters on mobile. But the implementation is different:

## Theme / design tokens

All visual values come from the theme:
  import { colors, spacing, typography } from "@/theme";

Do NOT hardcode hex colors, font sizes, or spacing values.
Use the theme constants:
  colors.primary, colors.background, colors.textMuted
  spacing.xs (4), spacing.sm (8), spacing.md (16), spacing.lg (24), spacing.xl (32)
  typography.heading, typography.body, typography.caption

Dark mode: use useColorScheme() hook and select from colors.light / colors.dark palettes.

State management patterns

Mobile apps have state management needs that differ from web. You have offline state, background/foreground transitions, push notification payloads, and device-specific data like permissions and location.

## State management

Server state (API data):
- Use React Query (TanStack Query) for all API calls
- Configure staleTime and gcTime based on data freshness needs
- Use queryClient.invalidateQueries() after mutations, not manual cache updates
- Enable offline persistence with @tanstack/query-async-storage-persister

Local/global state:
- Use Zustand for global app state (auth, user preferences, UI state)
- Do NOT use Redux - this project uses Zustand
- Do NOT use React Context for frequently changing values (causes re-renders)

Per-component state:
- Use useState for UI-only state (form inputs, toggle visibility, loading spinners)
- Use useReducer for complex local state with multiple related values

Persistence:
- Use MMKV (react-native-mmkv) for synchronous key-value storage
- Do NOT use AsyncStorage for frequently accessed data (it is async and slower)
- Persist Zustand stores with zustand/middleware and MMKV storage adapter

The distinction between server state and local state trips up AI tools regularly. Without this rule, the AI might store API responses in Zustand or use React Query for local UI toggles. Being explicit about which tool handles which type of state eliminates that confusion.


Handling permissions and device APIs

Mobile apps interact with device hardware: camera, location, biometrics, push notifications. Every one of these requires explicit permission handling that AI tools skip without guidance.

## Permissions and device APIs

Always request permissions before accessing device features:
1. Check current permission status first
2. Show an explanatory UI before the system prompt (pre-permission screen)
3. Request the permission
4. Handle denial gracefully with fallback UI

Use these libraries for device APIs:
- Camera: expo-camera
- Location: expo-location
- Notifications: expo-notifications
- Biometrics: expo-local-authentication
- File system: expo-file-system
- Haptics: expo-haptics

Rules:
- Never assume a permission is granted - always check first
- Handle the "denied permanently" case (direct user to Settings)
- Request permissions lazily (when needed), not eagerly (on app launch)
- All permission logic lives in src/hooks/usePermission.ts pattern

The "lazy permission" rule is especially important. AI tools tend to request all permissions on app startup, which triggers rejection from both users and app store reviewers.


Testing mobile apps

Testing React Native code requires a different setup than testing web React. The rendering environment is different, and you need to account for native modules, navigation state, and platform-specific behavior.

## Testing

Use Jest + React Native Testing Library. Test files live alongside source:
  src/screens/home-screen.tsx
  src/screens/home-screen.test.tsx

Component tests:
- Use render() from @testing-library/react-native (NOT @testing-library/react)
- Query with getByText, getByRole, getByLabelText
- Use fireEvent for user interactions, waitFor for async updates
- Wrap components in necessary providers (NavigationContainer, QueryClientProvider)

Navigation tests:
- Test that button presses trigger the expected navigation.navigate() call
- Mock navigation with jest.fn() passed as prop or via @react-navigation/native mock

API/hook tests:
- Use renderHook() from @testing-library/react-native
- Mock API calls with msw (Mock Service Worker) or jest.mock()
- Test loading, success, and error states for every query/mutation

Do NOT:
- Snapshot test screens (too brittle with native components)
- Test styling values directly
- Import from @testing-library/react (web version - wrong package)

The wrong testing library import is a frequent AI mistake. @testing-library/react and @testing-library/react-native have similar APIs but are not interchangeable. A single rule line prevents builds from breaking.


A complete rules file for React Native

Here is a starter rules file you can drop into a React Native project:

# Project: [Your App Name]

## Stack
- React Native 0.76, Expo SDK 52 (managed workflow)
- TypeScript 5 (strict mode)
- React Navigation v7
- Zustand + React Query
- React Native Testing Library + Jest

## Navigation
- Typed RootStackParamList in src/navigation/types.ts
- Always use typed navigation hooks
- Do NOT use react-router or any web router

## Components
- Function declarations with typed props
- One component per file, kebab-case filenames
- Use SafeAreaView from react-native-safe-area-context on all screens

## Styling
- StyleSheet.create() for all styles
- Design tokens from src/theme/tokens.ts
- No inline style objects, no hardcoded colors
- No "px" units - plain numbers only

## State
- React Query for server/API state
- Zustand for global app state
- MMKV for synchronous persistence
- No Redux, no Context for frequently changing state

## Platform
- Platform-specific files (.ios.tsx / .android.tsx) for major differences
- Platform.select() for minor differences
- Test on both iOS and Android

## Testing
- Jest + @testing-library/react-native
- Test files alongside source (.test.tsx)
- Mock navigation and API calls
- No snapshot tests

## Do NOT
- Do NOT import from "react-native" for SafeAreaView or AsyncStorage
- Do NOT use web CSS patterns (class names, media queries, CSS Grid)
- Do NOT request permissions on app launch
- Do NOT use Dimensions.get() for responsive layout - use useWindowDimensions()

Flutter and other frameworks

While this guide focuses on React Native, the principles apply to any mobile framework. If you are building with Flutter, your rules need equivalent specifics:

  • Widget composition patterns (StatelessWidget vs StatefulWidget vs ConsumerWidget with Riverpod)
  • Navigation: GoRouter or auto_route configuration with typed routes
  • State management: Riverpod, BLoC, or Provider, with explicit rules about which to use
  • Platform channels for native API access
  • Widget testing with testWidgets and the flutter_test package

The key principle is the same: AI models have less training data for mobile frameworks than for web frameworks, so your rules need to be more specific and more example-heavy to compensate.


Keeping mobile rules in sync

Mobile projects move fast. React Native releases new architecture features, Expo bumps SDK versions, and libraries change APIs. Your rules need to keep up.

The manual approach works for a solo developer: update the file, commit, done. But if your team has multiple mobile developers working across several apps, you need a way to share and version rules centrally.

This is exactly what localskills.sh handles. Publish your React Native rules once, and every developer installs the same version. When you upgrade from Expo SDK 51 to 52, publish an updated skill. Everyone pulls it with one command.

You can see how other teams structure their rules in real-world cursorrules examples, and read the foundational principles in AI coding rules best practices. If your mobile app lives in a larger monorepo, the monorepo rules guide covers how to layer mobile-specific rules on top of shared conventions.

The format works across tools automatically. One skill installs into Cursor, Claude Code, Windsurf, and others:

localskills install your-team/react-native-rules --target cursor claude windsurf

No more copying files between projects or hoping everyone read the wiki.


Ready to standardize your mobile development rules across your team and every AI coding tool? Create your free account and publish your first skill today.

npm install -g @localskills/cli
localskills login
localskills publish