src/skills/mobile-styling-nativewind/SKILL.md
NativeWind v4+ - Tailwind CSS utility classes for React Native, className prop, CSS variables, dark mode, platform prefixes, animations, theming, third-party component integration
npx skillsauth add agents-inc/skills mobile-styling-nativewindInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Quick Guide: NativeWind brings Tailwind CSS utility classes to React Native via
classNameprop. Styles compile toStyleSheet.createat build time with a lightweight runtime for conditional logic (dark mode, hover, focus). Always declare both light AND dark styles (no CSS cascade in RN). Usevars()for runtime theming with CSS variables. Platform prefixes (ios:,android:,native:) replacePlatform.selectfor styling. UseremapPropsfor third-party components with multiple style props; reservecssInteropfor components needing style-to-prop extraction.
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST always declare BOTH light and dark styles -- className="text-black dark:text-white" not just className="dark:text-white" -- React Native has no CSS cascade)
(You MUST use remapProps for third-party components with multiple style props and cssInterop ONLY when style attributes need extraction to props -- NEVER use either for your own custom components)
(You MUST import "./global.css" at your app entry point -- without it no styles render)
(You MUST add /// <reference types="nativewind/types" /> in a nativewind-env.d.ts file for TypeScript className support)
(You MUST use nativewind/preset in tailwind.config.js presets -- without it platform-specific features break)
</critical_requirements>
Auto-detection: NativeWind, nativewind, className on React Native components, nativewind/preset, nativewind/babel, nativewind/metro, withNativeWind, cssInterop, remapProps, vars(), useColorScheme from nativewind, useUnstableNativeVariable, dark: prefix in React Native, ios: prefix, android: prefix, native: prefix, global.css tailwind directives, nativewind-env.d.ts
When to use:
vars()ios:/android:/native: prefixesKey patterns covered:
useColorScheme (system preference and manual toggle)vars() and useUnstableNativeVariable()ios:, android:, web:, native:) for cross-platform stylingremapProps vs cssInterop)When NOT to use:
StyleSheet.create directly)Detailed Resources:
NativeWind bridges Tailwind CSS and React Native by compiling utility classes into StyleSheet.create objects at build time and providing a runtime for conditional style logic (dark mode, hover states, focus). The className prop works directly on React Native core components via a JSX transform -- no wrapper components needed.
Core principles:
StyleSheet.create objects, keeping runtime overhead minimal (~2ms per render vs 0ms for raw StyleSheet)className available inside your components, enabling compatibility with class merging libraries (clsx, tailwind-variants, cva)text-black dark:text-white, not just dark:text-white)ios:shadow-lg android:elevation-4 is more declarative than wrapping in Platform.selectcssInterop or remapProps on your own components. Simply accept a className prop and merge it with defaultsremapProps (lightweight) or cssInterop (full runtime) only for third-party components that need className supportArchitecture:
NativeWind's JSX transform intercepts component rendering. On native, it resolves className strings into StyleSheet.create IDs and applies conditional logic. On web, it passes className through as standard CSS. This means:
react-native-reanimated is a peer dependency (powers animations and transitions)react-native-safe-area-context is a peer dependency (used for safe area utilities)tailwindcss ^3.4 is required (v4 uses Tailwind CSS v3 config format; NativeWind v5 targets Tailwind CSS v4)style props merge with className-based styles, with inline taking precedencerem units: NativeWind uses rem: 14 on native (matching React Native's default 14px font size) and rem: 16 on web. Specify 10px in theme config and let NativeWind normalize per platform.
All React Native core components accept className after installing NativeWind. Styles compile at build time -- no runtime string parsing in production.
import { View, Text, Pressable } from "react-native";
export function Card({
title,
onPress,
}: {
title: string;
onPress: () => void;
}) {
return (
<View className="rounded-lg bg-white p-4 shadow-md dark:bg-gray-800">
<Text className="text-lg font-bold text-gray-900 dark:text-white">
{title}
</Text>
<Pressable
className="mt-3 rounded-md bg-blue-500 px-4 py-2 active:bg-blue-600"
onPress={onPress}
>
<Text className="text-center font-medium text-white">View Details</Text>
</Pressable>
</View>
);
}
Why good: Both light and dark variants declared, active: pseudo-class for press feedback, no inline style objects, compile-time resolution
See examples/core.md for custom component patterns with className merging and variant props.
Accept a className prop and merge it with defaults. Never use cssInterop or remapProps on your own components.
interface BadgeProps {
label: string;
variant?: "info" | "success" | "warning" | "error";
className?: string;
}
const VARIANT_CLASSES = {
info: "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200",
success: "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200",
warning:
"bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200",
error: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200",
} as const;
export function Badge({ label, variant = "info", className }: BadgeProps) {
return (
<Text
className={`rounded-full px-2 py-1 text-xs font-medium ${VARIANT_CLASSES[variant]} ${className ?? ""}`}
>
{label}
</Text>
);
}
Why good: className prop enables external overrides, variant map is a named constant, both light and dark styles declared per variant
When to use: For complex variant logic, use a class merging library (clsx, tailwind-variants, cva) to handle conditional classes and conflict resolution.
See examples/core.md for patterns with clsx and tailwind-variants.
NativeWind follows the system color scheme by default. Use dark: prefix for dark-mode styles. Use useColorScheme() from nativewind to read or manually set the scheme.
import { useColorScheme } from "nativewind";
import { View, Text, Pressable } from "react-native";
export function ThemeToggle() {
const { colorScheme, toggleColorScheme } = useColorScheme();
return (
<View className="flex-1 items-center justify-center bg-white dark:bg-gray-900">
<Text className="text-lg text-gray-900 dark:text-white">
Current: {colorScheme}
</Text>
<Pressable
className="mt-4 rounded-md bg-gray-200 px-4 py-2 dark:bg-gray-700"
onPress={toggleColorScheme}
>
<Text className="text-gray-900 dark:text-white">Toggle Theme</Text>
</Pressable>
</View>
);
}
Why good: useColorScheme from nativewind (not react-native) provides toggleColorScheme and setColorScheme, system preference followed by default
Gotcha: For Expo apps, userInterfaceStyle must be set to "automatic" in app.json for system preference to work.
See examples/theming.md for manual theme persistence and multi-theme patterns with vars().
Use vars() to set CSS variable values that flow down the component tree via React Context. Use useUnstableNativeVariable() to read resolved values in JavaScript.
import { vars, useUnstableNativeVariable } from "nativewind";
import { View, Text, ActivityIndicator } from "react-native";
const brandTheme = vars({
"--color-primary": "#3b82f6",
"--color-primary-text": "#ffffff",
"--color-surface": "#f8fafc",
});
export function ThemedScreen() {
return (
<View style={brandTheme} className="flex-1 bg-[--color-surface]">
<Text className="text-lg font-bold text-[--color-primary]">
Branded Content
</Text>
<ThemedSpinner />
</View>
);
}
// useUnstableNativeVariable reads resolved CSS variable values
function ThemedSpinner() {
const primaryColor = useUnstableNativeVariable("--color-primary");
return <ActivityIndicator color={primaryColor} />;
}
Why good: vars() returns a style object applied to ancestor, children resolve variables via context, useUnstableNativeVariable bridges CSS variables to props that don't accept className
See examples/theming.md for multi-brand theming and combining vars() with useColorScheme.
Use ios:, android:, web:, and native: prefixes to apply styles per platform. The native: prefix targets all platforms except web.
<View className="p-4 ios:pt-12 android:pt-8">
<Text className="text-base ios:font-semibold android:font-bold">
Platform-aware text
</Text>
<View className="ios:shadow-lg android:elevation-4 rounded-lg bg-white p-4">
<Text className="text-gray-900">Card with platform shadows</Text>
</View>
</View>
Why good: Declarative platform branching in className, no Platform.select boilerplate for styling, shadows handled correctly per platform (iOS ignores elevation, Android ignores shadow props)
See examples/platform-and-interop.md for complex platform patterns.
Use remapProps (lightweight, no runtime cost) to map className props to style props. Use cssInterop (full runtime, performance cost) only when style attributes need extraction to individual props.
import { remapProps, cssInterop } from "nativewind";
import { FlatList, TextInput } from "react-native";
// remapProps: maps className strings to style props (lightweight)
remapProps(FlatList, {
className: "style",
contentContainerClassName: "contentContainerStyle",
columnWrapperClassName: "columnWrapperStyle",
});
// cssInterop: extracts style attributes to props (full runtime)
cssInterop(TextInput, {
className: {
target: "style",
nativeStyleToProp: { textAlign: true },
},
placeholderClassName: {
target: false,
nativeStyleToProp: { color: "placeholderTextColor" },
},
});
Why good: remapProps has zero style resolution overhead, cssInterop used only when style attributes must become individual props (like placeholderTextColor)
When to use: remapProps for components with multiple style props (FlatList, ScrollView). cssInterop only when a third-party component needs style properties extracted as individual props (TextInput placeholderTextColor, StatusBar backgroundColor).
See examples/platform-and-interop.md for TypeScript declarations, SVG integration, and the decision framework.
NativeWind supports Tailwind animation and transition classes, powered by react-native-reanimated under the hood. No need for Animated.View -- NativeWind creates animated versions automatically.
// Built-in animation classes
<View className="animate-spin h-8 w-8 rounded-full border-2 border-blue-500 border-t-transparent" />
<View className="animate-pulse rounded-lg bg-gray-200 p-4 dark:bg-gray-700" />
<View className="animate-bounce">
<Text className="text-2xl">Bounce</Text>
</View>
// Transitions: smooth interpolation when classes change
<Pressable className="rounded-md bg-blue-500 p-4 transition-colors duration-200 active:bg-blue-700">
<Text className="text-white">Press me</Text>
</Pressable>
Why good: Standard Tailwind animation classes work without Animated wrappers, transitions powered by reanimated for native performance
Gotcha: Animation and transition support is experimental on native. Animations currently only work with the style prop (not all mapped props). Transitions for shadow are web-only.
<decision_framework>
Need Tailwind utility classes in React Native?
├─ YES → NativeWind
└─ NO → StyleSheet.create (zero overhead)
Need zero runtime overhead?
├─ YES → StyleSheet.create (0ms)
├─ Acceptable ~2ms → NativeWind (compiled)
└─ Runtime parsing OK → twrnc (~8-15ms, pure runtime)
Does the component accept className already?
├─ YES → Use it directly (no setup needed)
└─ NO → Does it have multiple style props (style, contentContainerStyle)?
├─ YES → remapProps (lightweight, zero overhead)
└─ NO → Does a style attribute need to become a prop?
├─ YES → cssInterop (extracts style attributes to props)
└─ NO → remapProps with simple mapping
Static theme (compile-time)?
├─ YES → Customize tailwind.config.js theme.extend
└─ NO → Need runtime theme switching?
├─ YES → vars() with CSS variables
└─ Need multiple brand themes?
└─ Combine vars() + useColorScheme for brand + light/dark matrix
See reference.md for full API cheat sheet, dark mode strategy tree, and migration notes.
</decision_framework>
<red_flags>
High Priority Issues:
dark:text-white without text-black) -- React Native has no CSS cascade, so the light variant will have no text colorcssInterop or remapProps on your own custom components -- these are exclusively for third-party components. Your own components should accept and merge className directlyimport "./global.css" at app entry point -- no styles will render without itnativewind/preset in tailwind.config.js presets -- platform prefixes, CSS variable support, and other NativeWind-specific features will not workuseColorScheme from react-native instead of nativewind -- the nativewind version provides setColorScheme and toggleColorSchemeMedium Priority Issues:
cssInterop when remapProps would suffice -- cssInterop has runtime overhead for style resolution, event handlers, and context injectionnativewind.d.ts -- it conflicts with the package's own types. Use nativewind-env.d.tsuserInterfaceStyle: "automatic" in Expo app.json -- system dark mode preference will not be detectedsm:, md:, lg:) without customizing for mobile -- NativeWind's default breakpoints are web-centric (640px, 768px, 1024px) and may not match mobile screen sizesGotchas & Edge Cases:
style prop takes precedence over className styles due to CSS specificity -- <Text className="text-white" style={{ color: "black" }} /> renders blackrem units differ between platforms: 14 on native (RN default font size), 16 on web -- use px values in theme config for consistencycorePlugins in tailwind.config.js if you need bg-blue-500/50 syntaxvars() values propagate via React Context, not actual CSS -- they only flow to React children, not portal-rendered contentuseUnstableNativeVariable API may change in future versions (prefixed "unstable" intentionally)transition-shadow is web-only, and animation performance is actively being improvedgap- compiles to native columnGap/rowGap in v4 (v2 used a polyfill) -- verify your React Native version supports gap layout propsdivide- and space- utilities are temporarily unavailable in v4cssInterop/remapProps in favor of styled(), and vars() in favor of VariableContextProvider -- check migration guide when upgrading</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md
(You MUST always declare BOTH light and dark styles -- className="text-black dark:text-white" not just className="dark:text-white" -- React Native has no CSS cascade)
(You MUST use remapProps for third-party components with multiple style props and cssInterop ONLY when style attributes need extraction to props -- NEVER use either for your own custom components)
(You MUST import "./global.css" at your app entry point -- without it no styles render)
(You MUST add /// <reference types="nativewind/types" /> in a nativewind-env.d.ts file for TypeScript className support)
(You MUST use nativewind/preset in tailwind.config.js presets -- without it platform-specific features break)
Failure to follow these rules will cause invisible styles, broken dark mode, TypeScript errors on className props, and platform-specific rendering failures.
</critical_reminders>
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Mapbox GL JS interactive maps - map initialization, markers, popups, sources, layers, expressions, clustering, 3D terrain, geocoding, directions
tools
Leaflet interactive maps - map setup, tile layers, markers, popups, GeoJSON, custom controls, plugins, clustering, events