Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,11 @@ By adding selected `.mdc` files to `.cursor/rules/`, you can use these rules dir

- [Android Native (Jetpack Compose)](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/android-jetpack-compose-cursorrules-prompt-file.mdc) - Android development with Jetpack Compose integration.
- [Cursor Rules Pack v2](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/cursor-rules-pack-v2-cursorrules-prompt-file.mdc) - 7 sample production-tested rules (dependency discipline, error handling, state management, webhook security, and more). See the pack README for full-pack details.
- [Expo Router (React Native, Expo)](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/expo-router-cursorrules-prompt-file.mdc) - File-based routing patterns for Expo: layouts, route groups, dynamic and catch-all routes, typed routes, modals, auth gating, and web compatibility.
- [Flutter Expert](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/flutter-app-expert-cursorrules-prompt-file.mdc) - Flutter development with expert integration.
- [HarmonyOS ArkTS](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/harmony-arkts.mdc) - Components, state, resources, lifecycle, layout, and accessibility.
- [NativeScript](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/nativescript-cursorrules-prompt-file.mdc) - Cross-platform mobile app development.
- [NativeWind (React Native, Expo, Tailwind)](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/nativewind-cursorrules-prompt-file.mdc) - Tailwind-in-RN setup, className vs style, what does and doesn't translate from web Tailwind, dark mode, semantic tokens, and platform-specific variants.
- [React Native Expo](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/react-native-expo-cursorrules-prompt-file.mdc) - Expo-based mobile app development.
- [SwiftUI Guidelines](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/swiftui-guidelines-cursorrules-prompt-file.mdc) - SwiftUI development guidelines.
- [TypeScript (Expo, Jest, Detox)](https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/typescript-expo-jest-detox-cursorrules-prompt-file.mdc) - TypeScript development with Expo, Jest, and Detox integration.
Expand Down
94 changes: 94 additions & 0 deletions rules/expo-router-cursorrules-prompt-file.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
description: "Cursor rules for file-based routing with Expo Router in React Native and Expo apps (web + native)."
globs: ["app/**/*.{ts,tsx,js,jsx}", "**/_layout.{ts,tsx}"]
alwaysApply: false
---

# Expo Router

Expo Router is file-based routing for React Native. Every file inside `app/` becomes a route. Layouts wrap nested routes. The same code runs on iOS, Android, and web.

## Directory structure

- `app/_layout.tsx` is the root layout. It runs once at app start and must render `<Slot />`, `<Stack />`, `<Tabs />`, or `<Drawer />`.
- `app/index.tsx` is the home route (`/`).
- Nested folders create nested URLs: `app/settings/profile.tsx` → `/settings/profile`.
- `app/_layout.tsx` inside a subfolder defines a layout scoped to that subtree.
- Files starting with `_` are not routes (they're layouts or helpers).
- Files starting with `+` are special (e.g., `+not-found.tsx`, `+native-intent.tsx`).

## Layouts: Stack, Tabs, and groups

```tsx
// app/_layout.tsx
import { Stack } from "expo-router";
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="index" options={{ title: "Home" }} />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="modal" options={{ presentation: "modal" }} />
</Stack>
);
}
```

- Use `<Tabs>` for bottom tab navigators, `<Stack>` for pushed screens, `<Drawer>` for side drawers (requires `expo-router/drawer`).
- Wrap a layout in a route group `(name)` to share a layout without affecting the URL: `app/(tabs)/home.tsx` → `/home`. Groups are how you avoid `/tabs/home` URLs.

## Dynamic and catch-all routes

- `app/post/[id].tsx` → `/post/123`. Read with `useLocalSearchParams<{ id: string }>()`.
- `app/blog/[...slug].tsx` is a catch-all, captures `slug` as `string[]`.
- Optional catch-all: `app/blog/[[...slug]].tsx` matches both `/blog` and `/blog/a/b`.

## Linking and navigation

- `<Link href="/settings">` is the declarative way. It compiles to `<a>` on web, `<Pressable>` on native.
- `useRouter()` gives imperative `router.push()`, `router.replace()`, `router.back()`, `router.dismiss()`.
- Prefer `<Link>` for static destinations — it gets correct accessibility on web.
- Use `router.replace()` for auth flows so the user can't swipe-back to the login screen.

## Search params and typed routes

- Read query params with `useLocalSearchParams<{ q?: string }>()`. They are always strings.
- For type safety across the whole router, enable typed routes in `app.json`: `"experiments": { "typedRoutes": true }`. Then `href` is autocompleted and unknown paths are type errors.
- `useGlobalSearchParams` (rarely needed) reads params from the closest matching route, not the current screen.

## Modals and presentation styles

- Modal: `<Stack.Screen name="modal" options={{ presentation: "modal" }} />`. On iOS this slides up; on web it renders as a normal page (style yourself if you want overlay behavior).
- Transparent modal: `presentation: "transparentModal"` with a Pressable backdrop.
- Always provide a close affordance — modals on Android close on hardware back, but not all web browsers respect that.

## Authentication patterns

- Use `<Redirect href="/login" />` inside layouts to gate subtrees.
- For an auth context, wrap providers in `app/_layout.tsx` so they're available to every route.
- After login, call `router.replace("/(tabs)")` so the auth stack isn't in history.
- Don't read auth state inside the layout's render and conditionally render `<Redirect>` and routes in the same pass — extract a guard component to avoid flicker.

## Web compatibility

- Files must compile for native AND web. Don't import native-only APIs at the top of a route file — lazy-load with `Platform.OS === 'web'` checks or use `.web.tsx` / `.native.tsx` extensions.
- For SEO on web, set `<head>` tags inside the route: `import { Stack } from "expo-router"; <Stack.Screen options={{ title: "Page title" }} />` translates to `<title>` on web.
- Deep linking: configure `scheme` in `app.json`. The router uses the same URL structure on web and native — `/post/123` matches both.

## Common pitfalls

- "Route not found": every leaf route file must default-export a component. Layouts must default-export too.
- "useLocalSearchParams undefined": params are strings only. Parse to numbers manually. They are `undefined` on the first render of a freshly-pushed route — guard for it.
- "Two routes match": avoid having both `app/post/[id].tsx` and `app/post/index.tsx` if the index should redirect — use `<Redirect>` explicitly.
- "Tabs render briefly during auth check": move the auth check into a layout that renders a loading state, not into the tab screens themselves.
- "Web hot reload loses scroll": expected — use `scrollRestoration` patterns or `react-native-screens` scroll restoration on web.

## Don'ts

- Don't use React Navigation `useNavigation()` types directly. Use `useRouter()` from `expo-router` so types match.
- Don't manually call `useEffect(() => router.push(...))` for redirects on first render. Use `<Redirect>` instead — it's synchronous and SSR-safe.
- Don't put business logic in `_layout.tsx`. Layouts should be thin and only set up navigators, providers, and gates.

## Reference

- Docs: https://docs.expo.dev/router/introduction/
- API: https://docs.expo.dev/router/reference/
84 changes: 84 additions & 0 deletions rules/nativewind-cursorrules-prompt-file.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
description: "Cursor rules for styling React Native and Expo apps with NativeWind (Tailwind CSS for RN)."
globs: ["**/*.{ts,tsx,js,jsx}"]
alwaysApply: false
---

# NativeWind

NativeWind brings Tailwind CSS to React Native. You write `className="px-4 bg-primary"` on RN components, and NativeWind compiles to RN styles at build time.

## Setup essentials

- Babel: add `"nativewind/babel"` to plugins in `babel.config.js`.
- Metro: wrap the config with `withNativeWind(config, { input: "./global.css" })`.
- `global.css` must contain the three Tailwind directives (`@tailwind base; @tailwind components; @tailwind utilities;`) and be imported once from the root layout.
- `tailwind.config.js` must include `content: ["./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}"]` — missing paths means classes are tree-shaken away in production.
- Add `nativewind-env.d.ts` referencing `nativewind/types` so `className` is typed on RN components.

## className vs style

- Prefer `className` for everything that can be expressed statically. The compiler hoists styles, so it's faster than inline `style={...}`.
- Use `style` only for runtime-computed values (Reanimated shared values, dynamic positioning from gestures, theme tokens not yet expressed in Tailwind).
- Don't mix the two for the same property — pick one source of truth per style attribute.

## What does and doesn't translate to RN

NativeWind covers a strict subset of Tailwind. These work:

- Layout: `flex`, `flex-row`, `items-center`, `justify-between`, `gap-2`
- Spacing: `p-*`, `m-*`, `px-*`, `py-*`
- Sizing: `w-*`, `h-*`, `w-full`, `max-w-md`
- Colors and typography: `bg-*`, `text-*`, `font-bold`, `text-lg`
- Borders and radius: `border`, `border-2`, `rounded-md`, `border-border`
- Opacity, shadow (web only — for native use `shadow-*` with explicit color)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In current NativeWind docs, are opacity utilities supported on React Native, and which shadow utilities/behaviors differ between native and web?

💡 Result:

In current NativeWind documentation, opacity utilities (e.g., opacity-{n}, opacity-[n]) are supported on React Native [1]. Regarding shadow utilities, there are distinct differences between native and web behaviors due to React Native's underlying styling constraints [2][3][4]: 1. Supported Utilities: Standard shadow utilities such as shadow, shadow-{n}, and shadow-none are supported [2]. 2. Web-Only Utilities: Certain complex shadow utilities, including shadow-inner and arbitrary values (shadow-[n]), are explicitly marked as web-only in the documentation [2]. 3. Native-Specific Behavior: - Shadows on native platforms often require an explicit background color to render correctly [2][3]. - NativeWind maps Tailwind shadow classes to React Native-specific properties, primarily utilizing shadowOffset, shadowColor, and shadowOpacity on iOS, and elevation on Android [5][3][6]. - Users are advised that shadow inconsistencies across platforms can occur [7][6]. While newer React Native versions (0.79+) have introduced a cross-platform boxShadow prop, NativeWind's integration of these features is evolving as the ecosystem transitions toward the new architecture [5][6].

Citations:


Fix misleading “web only” wording on line 34 (scope it to shadow, not opacity).

Line 34 currently reads as if both opacity and shadow are web-only; NativeWind opacity utilities are supported on React Native. Split/reword so “web only” applies to shadow-specific semantics/utilities (e.g., shadow-inner / arbitrary shadow-[...]), while keeping opacity-* platform-appropriate (e.g., its own bullet).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rules/nativewind-cursorrules-prompt-file.mdc` at line 34, The current line
conflates opacity and shadow as "web only"; update the text so that platform
scope applies only to shadow semantics/utilities: create separate bullets or
clauses where `opacity-*` is described as supported on React Native (keep
`opacity-*` as platform-appropriate) and qualify shadow-specific items (e.g.,
`shadow-inner`, arbitrary `shadow-[...]` and shadow semantics) as "web only" for
NativeWind; reference the terms `opacity-*`, `shadow`, `shadow-inner`, and
`shadow-[...]` when editing so the web-only note is scoped to shadow only.

- State variants on `Pressable`: `active:bg-primary/80`

These do NOT work natively (web only):

- Pseudo-classes like `:hover`, `:focus-visible`, `:before`, `:after`
- CSS Grid (`grid`, `grid-cols-*`)
- `transition-*` / `animate-*` (use Reanimated instead)
- `cursor-*`, `select-*`

When you need cross-platform: guard with the `web:` prefix or `Platform.OS === 'web'`.

## Dark mode

- Class strategy is the default. Set `darkMode: 'class'` in `tailwind.config.js` (or `'media'` to auto-track system).
- Use `dark:bg-background-900` style classes — no per-theme component forking.
- Read the active scheme with `useColorScheme()` from `nativewind` (not from `react-native`), so the class toggles correctly.

## Theme tokens

- Define semantic tokens in `tailwind.config.js` `theme.extend.colors` — e.g., `primary`, `foreground`, `muted`, `card`. Reference these everywhere instead of raw `blue-500`, `gray-700`.
- Spacing scale uses Tailwind defaults (`0`, `0.5`, `1`, `2`, ... `96`). Don't introduce arbitrary values like `p-[13px]` — extend the scale instead.
- Tokens map to CSS variables on web automatically, so the same `bg-primary` works in browser and native.

## Platform-specific classes

NativeWind supports platform variants:

```tsx
<View className="p-4 ios:pt-6 android:pt-4 web:hover:bg-muted" />
```

Useful for status-bar offsets, hover states (web only), and Android-specific tap feedback.

## Performance

- Keep classNames static where possible. Conditional classes via `cn()` (clsx + tailwind-merge) are fine, but constructing className strings inside render hot paths with template literals leaks compile-time optimization.
- Lists: don't put complex className on every row in a large `FlatList` if the row recomputes. Extract a memoized row component.
- Animations: NativeWind cannot animate `bg-*` or other utility classes. Use Reanimated's `useAnimatedStyle` and inline `style` for the animated properties.

## Common pitfalls

- "Class doesn't apply": check that the file path is in `tailwind.config.js` `content` array, and that `global.css` is imported once from the root layout.
- "Type error on className": missing `nativewind-env.d.ts` or `tsconfig.json` not including it.
- "Colors look different on web vs native": likely raw RGB / hex vs CSS variable mismatch. Define colors with `hsl(var(--primary))` pattern to share definitions cleanly.
- "Reanimated values don't animate": don't try to animate Tailwind classes. Use `style={useAnimatedStyle(...)}` for the animated parts.

## Reference

- Docs: https://www.nativewind.dev
- Repo: https://github.com/nativewind/nativewind
Loading