10 Cursor Rules for React Native (Copy-Paste) (2026)
SWATI BARWAL
·18 min read
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):
# tested on: 2026-07-20 — run from Expo/RN project root
mkdir -p .cursor/rulesCreate each .mdc file below (or paste all ten). Commit them so the whole team gets the same agent behavior:
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.jsonAfter 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.
---
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
---
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
---
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):
---
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):
---
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.
---
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
---
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.
---
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
---
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
---
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.
---
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:
---
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
Cursor Rules Explained: Complete .cursorrules Guide (2026) — .mdc frontmatter, globs, and migration from .cursorrules
MCP Servers in Cursor: Setup and Security Guide (2026) — give the agent live tools without stuffing docs into rules
Build Your First MCP Server in TypeScript (2026) — custom MCP tools that complement project rules
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.
