rn-styling — guardrail for styling in React Native + Expo
The 5 rules (non-negotiable)
- NativeWind v4 is the default.
StyleSheetonly for performance-critical paths (per-frame animations etc.). - No magic numbers. Every spacing/color/radius/font value comes from
tailwind.config.js(which mirrors the project's DESIGN.md tokens). - SafeArea mandatory on every root screen. Use
SafeAreaViewfromreact-native-safe-area-context(NOT the one fromreact-native). - Dark mode via
useColorScheme+ Tailwinddark:variant. NeverAppearance.getColorScheme()at module top-level. - Optimized primitives:
expo-imageinstead ofImage;@shopify/flash-listfor any list > 20 items.
Quick decision tree
- "Should I style this with NativeWind or StyleSheet?" →
references/decision-tree.md - "How do I set up NativeWind v4 from scratch?" →
references/nativewind-setup.md - "Why does Flexbox behave differently than on the web?" →
references/concepts.md - "What are the common patterns and anti-patterns?" →
references/patterns.md - "Show me a real example" →
references/examples/ - "I need this screen to feel native, not just look styled" (grouped settings, native toggle/picker/slider, real bottom sheet) → this isn't a styling question, it's
references/expo-ui.md(@expo/ui) — real SwiftUI/Jetpack Compose components, not RN primitives.
NativeWind vs @expo/ui — two different tools
- NativeWind (this skill's default) styles RN's own primitives (
View,Text, …) with utility classes. It changes how things look. @expo/ui(seereferences/expo-ui.md; import paths verified against@expo/ui@57.0.14) renders actual native components — SwiftUI on iOS, Jetpack Compose on Android — not styled primitives. Reach for it when a screen needs to genuinely behave like a native settings/picker/sheet screen, not when it just needs the right colors and spacing.- They compose: an
@expo/uitree can sit inside a NativeWind-styled screen. Don't use@expo/uias a general-purpose styling system, and don't expect NativeWind classes to apply inside its native components.
Common anti-patterns (NEVER do)
- ❌
style={{ padding: 16 }}with a magic number — pull from token. - ❌ Root
<View>withoutSafeAreaViewfromreact-native-safe-area-context. - ❌
import "tailwindcss"directly in code — only NativeWind imports. - ❌
Appearance.getColorScheme()at module level — useuseColorScheme()hook. - ❌
Imagefromreact-nativefor remote URIs — useexpo-image. - ❌
flex: 1on root + scroll withoutcontentContainerStyleonScrollView— content gets clipped.
Sources
- Course: codewithbeto.dev/rnCourse — lesson 10 "Styling Your App" (free).
- Official: https://reactnative.dev/docs/style (RN's own styling reference) — ⚠️ the
docs.expo.dev/develop/user-interface/styling/page this line used to cite 404s (checked 2026-08-26); Expo's user-interface section now coverscolor-themes/,safe-areas/,fonts/andanimation/rather than a single styling page - Official: https://www.nativewind.dev/ (v4)
- Official: https://github.com/AppAndFlow/react-native-safe-area-context
- Official: https://docs.expo.dev/versions/latest/sdk/ui/ (
@expo/ui) — the page badges it Included in Expo Go and recommends~57.0.14, whileexpo@57.0.16pins~57.0.13; take the SDK's pin. Import paths and the 19 root exports verified at 57.0.14 — seereferences/expo-ui.md