All articles

10 Cursor Rules for React Native (Copy-Paste) (2026)

SWATI BARWAL

SWATI BARWAL

·18 min read

cursor

Cursor rules react native teams actually need look different from Next.js rules. Without mobile-scoped instructions, Cursor happily invents div, window.location, CSS modules, and fetch inside JSX — patterns that break Expo builds or tank list performance. This guide gives you ten copy-paste `.mdc` project rules for React Native and Expo, with globs, install steps for .cursor/rules/, and clear guidance on alwaysApply vs file scoping. Rules were shaped against Cursor’s official project rules docs and checked on 2026-07-20.

If you still need the mental model for .mdc frontmatter and migration from .cursorrules, read Cursor Rules Explained first — then come back and drop these RN files into your repo.

Author: Aneh Thakur · Last updated: 2026-07-20

What you'll learn

  • How to install ten React Native / Expo Cursor rules into .cursor/rules/

  • When to use alwaysApply: true vs globs for mobile file trees

  • Copy-paste .mdc rules for Expo Router, TypeScript strict, StyleSheet vs NativeWind, data fetching hooks, Jest/RNTL, Metro, expo-constants env, navigation typing, FlatList performance, and banned patterns

  • How to keep always-on rules short so Agent mode stays fast

  • Troubleshooting when rules do not load or conflict

  • FAQ answers for common “Cursor wrote web code in my RN app” failures

Prerequisites

  • Cursor with Project Rules support (.cursor/rules/*.mdc)

  • An Expo or React Native app opened at the workspace root (rules apply per root)

  • TypeScript enabled in the project (tsconfig.json with "strict": true preferred)

  • Comfort creating folders and committing .cursor/rules/ to git

  • Optional: NativeWind already installed if you choose the NativeWind styling rule over StyleSheet-only


How Cursor project rules work (30-second recap)

Cursor injects Project Rules from .cursor/rules/*.mdc based on frontmatter (docs):

Frontmatter

Behavior

Use for RN

alwaysApply: true

Every chat / agent turn

Stack identity, banned web APIs, security

globs: [...]

When matching files are in context

Expo Router, tests, Metro, FlatList screens

description + agent-requested

Agent decides when to pull the rule

Rare helpers (e.g. release checklist)

Manual @rule mention

You attach it

One-off migrations

Rule of thumb for mobile: one short always-on file for “this is Expo/React Native, not web,” then glob-scoped files for Router, tests, Metro, and list screens. Do not dump 3,000 words into a single always-apply rule — that burns context on every CSS tweak and README edit.


Install into `.cursor/rules/`

From your app root (the folder that contains package.json and app.json / app.config.ts):

Bash
# tested on: 2026-07-20 — run from Expo/RN project root
mkdir -p .cursor/rules

Create each .mdc file below (or paste all ten). Commit them so the whole team gets the same agent behavior:

Code
your-expo-app/
├── .cursor/
│   └── rules/
│       ├── 00-rn-stack.mdc
│       ├── 01-typescript-strict.mdc
│       ├── 02-expo-router.mdc
│       ├── 03-stylesheet-nativewind.mdc
│       ├── 04-data-fetching-hooks.mdc
│       ├── 05-testing-jest-rntl.mdc
│       ├── 06-metro-config.mdc
│       ├── 07-env-expo-constants.mdc
│       ├── 08-navigation-typing.mdc
│       ├── 09-flatlist-performance.mdc
│       └── 10-banned-patterns.mdc
├── app/                 # Expo Router
├── components/
├── package.json
└── tsconfig.json

After adding or editing rules: Command Palette → Developer: Reload Window. Then open a screen under app/ and ask Cursor to add a button — you should see RN/Expo patterns (Pressable, StyleSheet, typed routes), not div and onClick.

Numbering tip: Prefix filenames (00-, 01-, …) so humans see load order in the folder. Cursor does not guarantee apply order by filename; keep rules non-contradictory instead of relying on sequence.


alwaysApply vs globs for React Native

Use `alwaysApply: true` only for truths that must hold on *every* file:

  • Package manager and Expo vs bare RN

  • “No DOM APIs / no Next.js imports”

  • Secret handling and no hardcoded API keys

  • Prefer Pressable over deprecated Touchable* if that is your team standard

Use `globs` when the advice is file-type specific:

Concern

Example globs

Expo Router screens

app/**/*.{tsx,ts}, src/app/**/*.{tsx,ts}

Shared UI

components/**/*.{tsx,ts}

Tests

**/__tests__/**/*.{ts,tsx}, **/*.{test,spec}.{ts,tsx}

Metro

metro.config.js, metro.config.cjs, metro.config.ts

Env / config

app.config.ts, app.config.js, eas.json

Avoid alwaysApply for FlatList tuning or Jest matchers — those rules should wake up only when list screens or test files are relevant. That is the difference between a fast Agent session and a context-starved one.


Rule 1 — Stack identity (always apply)

Keep this under ~150 words. It stops Cursor from assuming Vite + React DOM.

Markdown
---
description: React Native / Expo stack identity — never invent web DOM APIs
alwaysApply: true
---

# React Native / Expo stack

- This is an **Expo (or React Native)** app — not Next.js, not React DOM.
- UI primitives: `View`, `Text`, `Pressable`, `ScrollView`, `FlatList`, `TextInput`, `Image` from `react-native`.
- Never use `div`, `span`, `button`, `onClick`, `className` for DOM, `window`, `document`, or `localStorage` unless a documented polyfill exists and is already in the repo.
- Navigation: **Expo Router** file-based routes under `app/` (or `src/app/`). Do not add `react-navigation` stacks unless the project already uses them.
- Styling: follow the project’s StyleSheet or NativeWind convention (see styling rule). Do not invent CSS modules or styled-components unless already present.
- Prefer TypeScript. Do not introduce `any` without a one-line justification comment.
- When unsure about an API, check `package.json` dependencies before suggesting a new library.

Save as .cursor/rules/00-rn-stack.mdc.


Rule 2 — TypeScript strict

Markdown
---
description: TypeScript strict mode for React Native — no silent any, typed props
globs:
  - "**/*.{ts,tsx}"
alwaysApply: false
---

# TypeScript strict (React Native)

- Respect `tsconfig` strict settings. Prefer explicit prop types for every component.
- Use `type` for props objects; export props when the component is shared.
- Avoid `as any`. Prefer `unknown` + narrowing, or generated types from APIs.
- Platform files: `foo.ios.tsx` / `foo.android.tsx` / `foo.web.tsx` — keep shared types in `foo.tsx` or a `types.ts` next to them.
- Hooks: name custom hooks `useX`; return tuples or objects with stable shapes.
- Do not disable ESLint `@typescript-eslint/no-explicit-any` project-wide to “make the AI happy.”
- Path aliases: only use aliases already defined in `tsconfig` / `babel.config` (e.g. `@/`).

Save as .cursor/rules/01-typescript-strict.mdc.


Rule 3 — Expo Router

Markdown
---
description: Expo Router file-based routing — layouts, links, typed routes
globs:
  - "app/**/*.{ts,tsx}"
  - "src/app/**/*.{ts,tsx}"
alwaysApply: false
---

# Expo Router

- Routes live under `app/` (or `src/app/`). Prefer `_layout.tsx` for shared chrome (headers, tabs, stacks).
- Use `Link` / `router.push` / `router.replace` from `expo-router` — not raw React Navigation unless already wired.
- Prefer **typed routes** when the project has them enabled (`experiments.typedRoutes` in app config). Import `Href` types instead of stringly-typed paths when available.
- Dynamic segments: `app/users/[id].tsx` — read params with `useLocalSearchParams` and validate (string vs string[]).
- Groups: `(auth)`, `(tabs)` for organization without URL segments — do not over-nest groups.
- Loading/error: use `loading.tsx` / `error` boundaries where the Expo Router version supports them; otherwise colocate clear fallback UI.
- Do not put business logic in `_layout.tsx` beyond providers and navigation chrome.
- Deep links: keep path names stable; document any intentional rename in the PR description.

Save as .cursor/rules/02-expo-router.mdc.


Rule 4 — StyleSheet vs NativeWind

Pick one styling system and encode it. Dual systems confuse the model into mixing className and inline objects randomly.

Option A — StyleSheet-first (default for many Expo apps):

Markdown
---
description: React Native StyleSheet conventions — no inline style abuse
globs:
  - "**/*.{tsx,jsx}"
alwaysApply: false
---

# Styling — StyleSheet

- Prefer `StyleSheet.create` at the bottom of the file (or a colocated `styles.ts`).
- Avoid large inline `style={{ ... }}` objects in JSX except for truly one-off dynamic values (e.g. animated height).
- Compose with arrays: `style={[styles.card, disabled && styles.cardDisabled]}`.
- Do not invent NativeWind / Tailwind `className` unless `nativewind` is in `package.json`.
- Use platform-safe spacing; prefer tokens (constants) for colors and radii already used in the repo.
- Images: set explicit `width`/`height` or use known aspect-ratio patterns — avoid layout thrash.

Option B — NativeWind-first (only if installed):

Markdown
---
description: NativeWind / Tailwind className conventions for React Native
globs:
  - "**/*.{tsx,jsx}"
alwaysApply: false
---

# Styling — NativeWind

- Prefer `className` with NativeWind utilities already used in the codebase.
- Do not add raw `StyleSheet.create` for static layout unless needed for animated styles or third-party components that require `style`.
- Keep dynamic styles as small `style` overrides, not duplicated utility strings.
- Do not use web-only Tailwind plugins or CSS `@apply` files unless the project already has them.
- Verify `global.css` / Babel preset remains intact when suggesting config changes.

Save the option that matches your repo as .cursor/rules/03-stylesheet-nativewind.mdc.


Rule 5 — No fetch in components without hooks

Network calls buried in render or useEffect spaghetti are the #1 mess Cursor produces in RN screens.

Markdown
---
description: Data fetching belongs in hooks or query libs — not raw fetch in JSX
globs:
  - "app/**/*.{ts,tsx}"
  - "src/**/*.{ts,tsx}"
  - "components/**/*.{ts,tsx}"
alwaysApply: false
---

# Data fetching

- Do **not** call `fetch` / `axios` directly inside the component function body or JSX.
- Prefer: existing data library in the repo (TanStack Query, SWR, Relay, custom hooks) — match what `package.json` already has.
- If no query lib exists, create `useXQuery` / `useXMutation` hooks under `hooks/` and keep screens thin.
- Handle loading, error, and empty states explicitly for mobile (offline-friendly messaging).
- Cancel or ignore stale responses on unmount; do not setState after unmount.
- Never hardcode production API URLs or tokens in components — use env via expo-constants / existing config helpers.
- For mutations, disable double-submit (button `disabled` while pending).

Save as .cursor/rules/04-data-fetching-hooks.mdc.


Rule 6 — Testing with Jest and RNTL

Markdown
---
description: Jest + React Native Testing Library patterns
globs:
  - "**/__tests__/**/*.{ts,tsx}"
  - "**/*.{test,spec}.{ts,tsx}"
alwaysApply: false
---

# Testing — Jest / RNTL

- Use **@testing-library/react-native** (RNTL) queries — prefer `getByRole` / `getByText` / `getByLabelText` over testID-only when accessible roles exist.
- Prefer `userEvent` (or project-standard fireEvent helpers) over implementation-detail probing.
- Mock `expo-router` / navigation modules the way existing tests do — do not invent a new mock style per file.
- Keep tests deterministic: no real network; mock timers when testing debounced search.
- Co-locate tests as `Component.test.tsx` or under `__tests__/` — match the repo’s existing pattern.
- Do not snapshot huge trees unless the project already relies on snapshots for that file.
- When adding a component, include a minimal happy-path test if the folder already has tests.

Save as .cursor/rules/05-testing-jest-rntl.mdc.


Rule 7 — Metro config

Metro mistakes cause “works on my machine” cache hell. Scope this rule tightly.

Markdown
---
description: Metro bundler config changes — monorepo, SVG, and cache safety
globs:
  - "metro.config.*"
  - "babel.config.*"
alwaysApply: false
---

# Metro config

- Prefer minimal diffs to `metro.config.js` / `metro.config.ts`. Explain why a resolver or `watchFolders` change is required.
- Monorepos: follow Expo’s monorepo Metro guidance already linked in the repo README when present.
- Do not disable cache permanently (`RESET_CACHE` hacks) as a “fix” — document one-time `npx expo start -c` instead.
- Asset extensions / SVG transformers: only add if the dependency is already installed or the task explicitly requires it.
- Keep `babel.config.js` presets aligned with Expo (`babel-preset-expo`) unless bare RN requires otherwise.
- Never commit machine-specific absolute paths in Metro config.

Save as .cursor/rules/06-metro-config.mdc.


Rule 8 — Env with expo-constants

Markdown
---
description: Environment variables via Expo config and expo-constants — no leaked secrets
globs:
  - "app.config.*"
  - "app.json"
  - "eas.json"
  - "**/constants/**/*.{ts,tsx}"
  - "**/config/**/*.{ts,tsx}"
alwaysApply: false
---

# Env — expo-constants

- Public client env belongs in `app.config.ts` / `app.config.js` `extra` (or Expo public env patterns the project already uses).
- Read values with `expo-constants` (`Constants.expoConfig?.extra`) or the project’s existing wrapper — do not invent three competing helpers.
- Never commit `.env` secrets. Document required keys in `.env.example` only.
- Distinguish **public** vs **secret** values: anything in the client bundle is public — API secrets belong on a backend.
- EAS: use EAS Secrets / profiles in `eas.json` for build-time values; do not hardcode staging URLs in multiple files.
- When adding a new env key, update types for `extra` if the project types them.

Save as .cursor/rules/07-env-expo-constants.mdc.


Rule 9 — Navigation typing

Markdown
---
description: Typed navigation and route params for Expo Router / React Navigation
globs:
  - "app/**/*.{ts,tsx}"
  - "src/app/**/*.{ts,tsx}"
  - "**/navigation/**/*.{ts,tsx}"
alwaysApply: false
---

# Navigation typing

- Prefer Expo Router typed routes when enabled; avoid `as any` on `router.push`.
- Validate `useLocalSearchParams` — treat params as `string | string[] | undefined` until narrowed.
- For React Navigation (if present): keep a central param list type; do not redefine stack params per screen.
- Deep link params: coerce IDs to the right type before API calls (number vs string).
- Do not navigate during render. Navigation side effects belong in event handlers or effects with clear guards.
- After auth state changes, prefer a single redirect strategy (layout guard) over scattered `router.replace` calls.

Save as .cursor/rules/08-navigation-typing.mdc.


Rule 10 — FlatList performance + banned patterns

Split into performance + bans so Agent sees both when touching lists or general components.

Markdown
---
description: FlatList performance and banned React Native patterns
globs:
  - "**/*.{tsx,jsx}"
alwaysApply: false
---

# FlatList performance

- Prefer `FlatList` / `SectionList` for long lists — not `ScrollView` mapping huge arrays.
- Provide stable `keyExtractor`. Avoid index-only keys when items have IDs.
- Use `renderItem` callbacks that do not close over changing inline objects unnecessarily; extract `ListItem` components.
- Consider `getItemLayout` for fixed-height rows; set reasonable `windowSize` / `maxToRenderPerBatch` only when profiling shows need.
- Avoid anonymous heavy inline styles inside `renderItem`.
- Images in rows: cache-friendly sizing; do not decode full-resolution assets for thumbnails.

# Banned patterns

- Do not default-export messy god components with 500+ lines — prefer named exports for components/hooks when the codebase does.
- Do not abuse inline styles for entire screens (see styling rule).
- Do not add `console.log` noise in hot paths (list render, animations).
- Do not introduce class components unless maintaining legacy code.
- Do not use `TouchableOpacity` / `TouchableHighlight` for new UI if the project standardized on `Pressable`.
- Do not fetch inside `renderItem`.

Save as .cursor/rules/09-flatlist-performance.mdc and, if you prefer separation, move the banned-patterns section into .cursor/rules/10-banned-patterns.mdc with the same globs:

Markdown
---
description: Banned React Native / Expo patterns Cursor must not introduce
globs:
  - "**/*.{tsx,jsx}"
  - "**/*.{ts,js}"
alwaysApply: false
---

# Banned patterns

- No DOM / web-only APIs in native screens (`div`, `document`, `localStorage` without a polyfill already in use).
- No default-export soup: avoid `export default function` dumping helpers, types, and UI in one anonymous blob when named exports are the team norm.
- No inline-style abuse for full layouts.
- No secrets in source.
- No new state libraries if one already exists (Redux vs Zustand vs Context — match the repo).
- No `fetch` in JSX / render paths (see data-fetching rule).

That is ten practical files: stack, TypeScript, Expo Router, styling, fetching, tests, Metro, env, navigation typing, and FlatList/bans.


Walkthrough — verify the rules load

1. Create the files under .cursor/rules/ as above. 2. Reload the Cursor window. 3. Open app/(tabs)/index.tsx (or your home route). 4. In Chat or Agent, ask: *“Add a primary CTA that navigates to /settings using Expo Router.”* 5. Confirm the draft uses Pressable or Link, typed href if available, and StyleSheet/NativeWind — not an <a> tag. 6. Open a *.test.tsx file and ask for a test — expect RNTL queries, not Enzyme or DOM Testing Library.

If the agent still suggests div, your always-apply stack rule is missing, too long and truncated, or the workspace root is a monorepo parent without the app’s .cursor/rules. Open the app package as the Cursor root, or add rules at the monorepo root with globs that include apps/mobile/**.


Context budget — keep RN rules lean

Mobile repos often add design-system docs, analytics SDKs, and EAS notes. Resist stuffing all of that into rules:

  • Always-on: ≤ 150–200 words

  • Per-area globs: ≤ 300 words each

  • Link to internal docs (“follow docs/design.md”) instead of pasting the whole design system

  • Delete rules that duplicate ESLint — rules should cover *agent judgment*, not what eslint --fix already enforces

Rules complement MCP. If you connect docs or issue trackers via MCP, keep coding conventions in .mdc files and live tool access in MCP — see MCP Servers in Cursor and Build your first MCP server in TypeScript.


Troubleshooting

Rules never seem to apply

Cause: Files live outside .cursor/rules/, wrong extension (.md instead of .mdc), or the opened folder is not the project root that contains .cursor.

Fix: Confirm path /.cursor/rules/*.mdc relative to the workspace root. Reload the window. In Cursor’s rule UI (Settings → Rules), check that project rules are listed.

Agent still writes `div` and CSS

Cause: Missing always-apply stack rule, or a User Rule that says “React web” overriding your intent in practice via conflicting guidance.

Fix: Add Rule 1 with alwaysApply: true. Shorten conflicting User Rules. Mention @00-rn-stack manually once to confirm attachment.

Expo Router rule loads on unrelated files

Cause: Globs too broad (**/*) or duplicated alwaysApply.

Fix: Narrow to app/**/*.{ts,tsx} and src/app/**/*.{ts,tsx}. Set alwaysApply: false on Router/testing/Metro rules.

NativeWind and StyleSheet fight each other

Cause: Both Option A and Option B installed, or className suggested without NativeWind installed.

Fix: Keep one styling rule. Delete the other. Align with package.json.

Type errors after Cursor “fixes” navigation

Cause: Untyped router.push("/foo") strings or wrong param shapes.

Fix: Ensure Rule 8 and typed routes / central param types exist. Ask the agent to validate params with narrowing helpers.

Metro config “fix” breaks the bundler

Cause: Agent added watchFolders or custom resolvers without monorepo context.

Fix: Revert metro.config.*, apply Rule 7, restart with npx expo start -c once, and require the agent to explain each Metro line.

Tests fail with “No QueryClient” or missing navigation mocks

Cause: New tests ignore existing providers/mocks.

Fix: Point Rule 6 at your jest.setup.ts patterns; ask the agent to copy the nearest sibling test’s wrappers.


Quick start checklist

1. Create .cursor/rules/ at the Expo app root (not a nested package unless that package is the workspace Cursor opens). 2. Paste the ten .mdc files from this guide — start with the always-on stack rule, then add globs for app/**, hooks, and lists. 3. Open Agent mode on one real screen and ask for a small change; reject any DOM or document APIs immediately. 4. After a week, delete rules the team never cites and tighten globs that load too often.

This loop keeps cursor rules react native projects lean: rules stay short, scoped, and tied to Expo Router, TypeScript, and FlatList habits your mobile team already enforces in review.

FAQ

  • What are the best Cursor rules for React Native in 2026?

  • Should Cursor rules use alwaysApply for Expo Router?

  • Do Cursor rules replace ESLint in a React Native project?

  • Can I use one .cursorrules file instead of .mdc?

  • How do I stop Cursor from adding inline styles everywhere?

  • Why does Cursor call fetch inside my screen components?

  • Do these rules work with bare React Native (no Expo)?

  • Should I commit .cursor/rules to git for a mobile team?

Related posts


Copy the ten .mdc files into .cursor/rules/, reload Cursor, and run one Agent task on a real screen. You should see fewer web hallucinations and more Expo-native diffs — that is the whole point of cursor rules react native teams can paste and ship.

SWATI BARWAL

Written by

SWATI BARWAL

1 follower

Related posts