Claude Skill

mobile-styling-nativewind

NativeWind v4+ - Tailwind CSS utility classes for React Native, className prop, CSS variables, dark mode, platform prefixes, animations, theming, third-party component integration

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download agents-inc-skills-dist_plugins_mobile-styling-nativewind_skills_mobile-styling-nativewind-3a51ef5.zip · 17 KB
Part of agents-inc/skills — 130 skills

Install

skills CLI npx skills add https://github.com/agents-inc/skills/tree/main/dist/plugins/mobile-styling-nativewind/skills/mobile-styling-nativewind
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install agents-inc-skills@llmmart
Git git clone https://github.com/agents-inc/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole agents-inc/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

NativeWind Patterns

Quick Guide: NativeWind brings Tailwind CSS utility classes to React Native via className prop. Styles compile to StyleSheet.create at 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). Use vars() for runtime theming with CSS variables. Platform prefixes (ios:, android:, native:) replace Platform.select for styling. Use remapProps for third-party components with multiple style props; reserve cssInterop for components needing style-to-prop extraction.


<critical_requirements>

CRITICAL: Before Using This Skill

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:

  • Styling React Native components with Tailwind CSS utility classes
  • Implementing dark mode with automatic system detection or manual toggle
  • Creating dynamic themes with CSS variables via vars()
  • Applying platform-specific styles with ios:/android:/native: prefixes
  • Integrating className support with third-party React Native libraries
  • Adding transitions and animations to React Native components

Key patterns covered:

  • className prop usage and custom component patterns
  • Dark mode with useColorScheme (system preference and manual toggle)
  • CSS variables for runtime theming via vars() and useUnstableNativeVariable()
  • Platform prefixes (ios:, android:, web:, native:) for cross-platform styling
  • Third-party component integration (remapProps vs cssInterop)
  • Animations and transitions (experimental, powered by react-native-reanimated)
  • Variant components with class merging libraries

When NOT to use:

  • Web-only React projects (use standard Tailwind CSS)
  • Projects that need zero runtime overhead (use StyleSheet.create directly)
  • Apps on legacy React Native architecture that cannot adopt New Architecture dependencies

Detailed Resources:




<decision_framework>

Decision Framework

Styling Approach

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)

Third-Party Component Integration

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

Theming Strategy

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>

RED FLAGS

High Priority Issues:

  • Declaring only dark styles without light counterpart (dark:text-white without text-black) -- React Native has no CSS cascade, so the light variant will have no text color
  • Using cssInterop or remapProps on your own custom components -- these are exclusively for third-party components. Your own components should accept and merge className directly
  • Missing import "./global.css" at app entry point -- no styles will render without it
  • Missing nativewind/preset in tailwind.config.js presets -- platform prefixes, CSS variable support, and other NativeWind-specific features will not work
  • Using useColorScheme from react-native instead of nativewind -- the nativewind version provides setColorScheme and toggleColorScheme

Medium Priority Issues:

  • Using cssInterop when remapProps would suffice -- cssInterop has runtime overhead for style resolution, event handlers, and context injection
  • Naming the TypeScript declaration file nativewind.d.ts -- it conflicts with the package's own types. Use nativewind-env.d.ts
  • Not setting userInterfaceStyle: "automatic" in Expo app.json -- system dark mode preference will not be detected
  • Using web-designed breakpoints (sm:, md:, lg:) without customizing for mobile -- NativeWind's default breakpoints are web-centric (640px, 768px, 1024px) and may not match mobile screen sizes

Gotchas & Edge Cases:

  • Inline style prop takes precedence over className styles due to CSS specificity -- <Text className="text-white" style={{ color: "black" }} /> renders black
  • rem units differ between platforms: 14 on native (RN default font size), 16 on web -- use px values in theme config for consistency
  • Color opacity is disabled by default for performance on native -- enable via corePlugins in tailwind.config.js if you need bg-blue-500/50 syntax
  • vars() values propagate via React Context, not actual CSS -- they only flow to React children, not portal-rendered content
  • useUnstableNativeVariable API may change in future versions (prefixed "unstable" intentionally)
  • Animations and transitions are experimental on native -- transition-shadow is web-only, and animation performance is actively being improved
  • gap- compiles to native columnGap/rowGap in v4 (v2 used a polyfill) -- verify your React Native version supports gap layout props
  • divide- and space- utilities are temporarily unavailable in v4
  • NativeWind v5 (in preview) deprecates cssInterop/remapProps in favor of styled(), and vars() in favor of VariableContextProvider -- check migration guide when upgrading
  • Tailwind CSS v4 is NOT yet supported by NativeWind v4 -- NativeWind v4 uses Tailwind CSS v3.4 config format

</red_flags>


<critical_reminders>

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>

Files (skills)
  • examples
    • core.md 9.1 KB
      # NativeWind - Core Patterns
      
      > className usage, custom components, variants, and conditional styling. See [SKILL.md](../SKILL.md) for decision guidance.
      
      **Prerequisites**: Familiarity with React Native components and Tailwind CSS utility class syntax.
      
      ---
      
      ## Pattern 1: Basic className Usage
      
      ```tsx
      import { View, Text, Pressable, Image, ScrollView } from "react-native";
      
      export function ProfileCard({
        name,
        bio,
        avatarUrl,
        onMessage,
      }: {
        name: string;
        bio: string;
        avatarUrl: string;
        onMessage: () => void;
      }) {
        return (
          <View className="mx-4 rounded-xl bg-white p-4 shadow-md dark:bg-gray-800">
            <View className="flex-row items-center gap-3">
              <Image source={{ uri: avatarUrl }} className="h-12 w-12 rounded-full" />
              <View className="flex-1">
                <Text className="text-lg font-bold text-gray-900 dark:text-white">
                  {name}
                </Text>
                <Text className="text-sm text-gray-500 dark:text-gray-400">
                  {bio}
                </Text>
              </View>
            </View>
            <Pressable
              className="mt-4 rounded-lg bg-blue-500 px-4 py-3 active:bg-blue-600"
              onPress={onMessage}
            >
              <Text className="text-center font-semibold text-white">Message</Text>
            </Pressable>
          </View>
        );
      }
      ```
      
      **Why good:** Both light and dark variants declared on every text/background, `active:` for press feedback, `gap-3` for spacing (compiles to native columnGap/rowGap), no style objects needed
      
      ---
      
      ## Pattern 2: Custom Component with className Prop
      
      Custom components should accept and merge a `className` prop. Never use `cssInterop` or `remapProps` on your own components.
      
      ```tsx
      import { View, Text, type ViewStyle } from "react-native";
      
      interface SectionProps {
        title: string;
        children: React.ReactNode;
        className?: string;
      }
      
      export function Section({ title, children, className }: SectionProps) {
        return (
          <View className={`mb-6 ${className ?? ""}`}>
            <Text className="mb-2 text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
              {title}
            </Text>
            {children}
          </View>
        );
      }
      
      // Usage
      <Section title="Account" className="px-4">
        <Text className="text-gray-900 dark:text-white">Settings content</Text>
      </Section>;
      ```
      
      **Why good:** className prop enables external customization, default styles set on the component, caller can override layout/spacing
      
      ---
      
      ## Pattern 3: Multiple className Props
      
      Complex components can expose multiple className props for different internal elements.
      
      ```tsx
      interface ListItemProps {
        title: string;
        subtitle?: string;
        onPress: () => void;
        className?: string;
        titleClassName?: string;
        subtitleClassName?: string;
      }
      
      export function ListItem({
        title,
        subtitle,
        onPress,
        className,
        titleClassName,
        subtitleClassName,
      }: ListItemProps) {
        return (
          <Pressable
            className={`flex-row items-center px-4 py-3 active:bg-gray-100 dark:active:bg-gray-800 ${className ?? ""}`}
            onPress={onPress}
          >
            <View className="flex-1">
              <Text
                className={`text-base text-gray-900 dark:text-white ${titleClassName ?? ""}`}
              >
                {title}
              </Text>
              {subtitle && (
                <Text
                  className={`mt-0.5 text-sm text-gray-500 dark:text-gray-400 ${subtitleClassName ?? ""}`}
                >
                  {subtitle}
                </Text>
              )}
            </View>
          </Pressable>
        );
      }
      ```
      
      **Why good:** Each internal element is independently customizable, defaults cover light and dark, no cssInterop needed
      
      ---
      
      ## Pattern 4: Variants with a Class Merging Library
      
      For components with multiple variant dimensions, use a class merging library to handle conditional classes and resolve conflicts.
      
      ### With clsx
      
      ```tsx
      import clsx from "clsx";
      import { Pressable, Text } from "react-native";
      
      type ButtonVariant = "primary" | "secondary" | "ghost";
      type ButtonSize = "sm" | "md" | "lg";
      
      interface ButtonProps {
        label: string;
        variant?: ButtonVariant;
        size?: ButtonSize;
        disabled?: boolean;
        onPress: () => void;
        className?: string;
      }
      
      const VARIANT_CLASSES: Record<ButtonVariant, string> = {
        primary: "bg-blue-500 active:bg-blue-600",
        secondary:
          "bg-gray-200 active:bg-gray-300 dark:bg-gray-700 dark:active:bg-gray-600",
        ghost: "bg-transparent active:bg-gray-100 dark:active:bg-gray-800",
      };
      
      const SIZE_CLASSES: Record<ButtonSize, string> = {
        sm: "px-3 py-1.5",
        md: "px-4 py-2.5",
        lg: "px-6 py-3.5",
      };
      
      const TEXT_VARIANT_CLASSES: Record<ButtonVariant, string> = {
        primary: "text-white",
        secondary: "text-gray-900 dark:text-white",
        ghost: "text-blue-500 dark:text-blue-400",
      };
      
      const TEXT_SIZE_CLASSES: Record<ButtonSize, string> = {
        sm: "text-sm",
        md: "text-base",
        lg: "text-lg",
      };
      
      export function Button({
        label,
        variant = "primary",
        size = "md",
        disabled = false,
        onPress,
        className,
      }: ButtonProps) {
        return (
          <Pressable
            className={clsx(
              "items-center rounded-lg",
              VARIANT_CLASSES[variant],
              SIZE_CLASSES[size],
              disabled && "opacity-50",
              className,
            )}
            disabled={disabled}
            onPress={onPress}
          >
            <Text
              className={clsx(
                "font-semibold",
                TEXT_VARIANT_CLASSES[variant],
                TEXT_SIZE_CLASSES[size],
              )}
            >
              {label}
            </Text>
          </Pressable>
        );
      }
      ```
      
      **Why good:** clsx handles conditional class concatenation cleanly, variant maps are named constants, disabled state is a simple conditional, caller can override via className
      
      ### With tailwind-variants
      
      ```tsx
      import { tv } from "tailwind-variants";
      import { Pressable, Text } from "react-native";
      
      const button = tv({
        base: "items-center rounded-lg",
        variants: {
          variant: {
            primary: "bg-blue-500 active:bg-blue-600",
            secondary: "bg-gray-200 active:bg-gray-300 dark:bg-gray-700",
            ghost: "bg-transparent active:bg-gray-100",
          },
          size: {
            sm: "px-3 py-1.5",
            md: "px-4 py-2.5",
            lg: "px-6 py-3.5",
          },
        },
        defaultVariants: {
          variant: "primary",
          size: "md",
        },
      });
      
      const buttonText = tv({
        base: "font-semibold",
        variants: {
          variant: {
            primary: "text-white",
            secondary: "text-gray-900 dark:text-white",
            ghost: "text-blue-500",
          },
          size: {
            sm: "text-sm",
            md: "text-base",
            lg: "text-lg",
          },
        },
        defaultVariants: {
          variant: "primary",
          size: "md",
        },
      });
      
      interface ButtonProps {
        label: string;
        variant?: "primary" | "secondary" | "ghost";
        size?: "sm" | "md" | "lg";
        onPress: () => void;
        className?: string;
      }
      
      export function Button({
        label,
        variant,
        size,
        onPress,
        className,
      }: ButtonProps) {
        return (
          <Pressable
            className={button({ variant, size, className })}
            onPress={onPress}
          >
            <Text className={buttonText({ variant, size })}>{label}</Text>
          </Pressable>
        );
      }
      ```
      
      **Why good:** tailwind-variants handles conflict resolution, default variants are declarative, className passthrough enables caller overrides
      
      ---
      
      ## Pattern 5: Conditional Styling
      
      ```tsx
      import clsx from "clsx";
      import { View, Text } from "react-native";
      
      interface StatusIndicatorProps {
        status: "online" | "offline" | "busy";
        unreadCount: number;
      }
      
      const MAX_DISPLAY_COUNT = 99;
      
      const STATUS_COLORS = {
        online: "bg-green-500",
        offline: "bg-gray-400",
        busy: "bg-red-500",
      } as const;
      
      export function StatusIndicator({ status, unreadCount }: StatusIndicatorProps) {
        const hasUnread = unreadCount > 0;
        const displayCount =
          unreadCount > MAX_DISPLAY_COUNT
            ? `${MAX_DISPLAY_COUNT}+`
            : String(unreadCount);
      
        return (
          <View className="flex-row items-center gap-2">
            <View className={clsx("h-3 w-3 rounded-full", STATUS_COLORS[status])} />
            {hasUnread && (
              <View className="min-w-[20px] items-center rounded-full bg-red-500 px-1.5 py-0.5">
                <Text className="text-xs font-bold text-white">{displayCount}</Text>
              </View>
            )}
          </View>
        );
      }
      ```
      
      **Why good:** Status colors are a named constant map, conditional rendering for badge, arbitrary value `min-w-[20px]` for minimum badge width, named constant for display limit
      
      ---
      
      ## Pattern 6: Inline Style Merging
      
      Inline `style` props merge with className-based styles. Inline properties take precedence.
      
      ```tsx
      import { View, Text, type ViewStyle } from "react-native";
      
      interface ProgressBarProps {
        progress: number; // 0-1
        className?: string;
      }
      
      export function ProgressBar({ progress, className }: ProgressBarProps) {
        // Dynamic width requires inline style -- className can't do runtime percentages
        const fillStyle: ViewStyle = { width: `${progress * 100}%` };
      
        return (
          <View
            className={`h-2 overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700 ${className ?? ""}`}
          >
            <View className="h-full rounded-full bg-blue-500" style={fillStyle} />
          </View>
        );
      }
      ```
      
      **Why good:** Static styles in className (background, height, border-radius), dynamic value in inline style (width percentage), inline style takes precedence over className
      
      **When to use inline style:** Runtime-computed values (dynamic widths, calculated positions, values from gestures). For everything else, prefer className.
      
    • platform-and-interop.md 7.8 KB
      # NativeWind - Platform Prefixes and Third-Party Interop
      
      > Platform-specific styling, cssInterop vs remapProps, and third-party component integration. See [core.md](core.md) for basic className patterns.
      
      **Prerequisites**: Understand [Pattern 5: Platform Prefixes](../SKILL.md) and [Pattern 6: Third-Party Integration](../SKILL.md) from SKILL.md.
      
      ---
      
      ## Pattern 1: Platform-Specific Styling
      
      Use `ios:`, `android:`, `web:`, and `native:` prefixes to handle platform differences declaratively.
      
      ```tsx
      import { View, Text, Pressable } from "react-native";
      
      export function PlatformCard({
        title,
        onPress,
      }: {
        title: string;
        onPress: () => void;
      }) {
        return (
          <View
            className={`
              m-4 rounded-xl bg-white p-4 dark:bg-gray-800
              ios:shadow-lg
              android:elevation-4
            `}
          >
            <Text
              className={`
                text-lg text-gray-900 dark:text-white
                ios:font-semibold
                android:font-bold
              `}
            >
              {title}
            </Text>
      
            {/* Haptic-style feedback differs by platform */}
            <Pressable
              className={`
                mt-3 rounded-lg bg-blue-500 px-4 py-3
                active:bg-blue-600
                android:active:bg-blue-700
              `}
              onPress={onPress}
            >
              <Text className="text-center font-medium text-white">Action</Text>
            </Pressable>
          </View>
        );
      }
      ```
      
      **Why good:** Shadows handled correctly per platform (iOS uses shadow-_, Android uses elevation-_), font weights adjusted for platform rendering, no Platform.select boilerplate
      
      ---
      
      ## Pattern 2: Native-Only and Web-Only Styles
      
      The `native:` prefix targets iOS + Android + all other native platforms (not web). Useful for cross-platform apps.
      
      ```tsx
      <View className="p-4 native:pt-12 web:pt-4">
        {/* Extra top padding on native for status bar area */}
        <Text className="text-xl font-bold text-gray-900 dark:text-white native:text-lg web:text-2xl">
          Responsive Heading
        </Text>
      
        {/* Hover only works on web (pointer devices) */}
        <Pressable className="rounded-lg bg-gray-100 p-3 active:bg-gray-200 web:hover:bg-gray-150">
          <Text className="text-gray-900 dark:text-white">Interactive Item</Text>
        </Pressable>
      </View>
      ```
      
      **Why good:** `native:` avoids repeating `ios: android:` for shared native behavior, `web:hover:` applies only where pointer events exist
      
      ---
      
      ## Pattern 3: remapProps for Multi-Style Components
      
      Use `remapProps` to map className props to style props on third-party components. This is lightweight -- no style resolution overhead.
      
      ```tsx
      import { FlatList, ScrollView, SectionList } from "react-native";
      import { remapProps } from "nativewind";
      
      // FlatList: maps className to multiple style props
      remapProps(FlatList, {
        className: "style",
        contentContainerClassName: "contentContainerStyle",
        columnWrapperClassName: "columnWrapperStyle",
        ListHeaderComponentClassName: "ListHeaderComponentStyle",
        ListFooterComponentClassName: "ListFooterComponentStyle",
      });
      
      // ScrollView: contentContainerStyle is common
      remapProps(ScrollView, {
        className: "style",
        contentContainerClassName: "contentContainerStyle",
        indicatorClassName: "indicatorStyle",
      });
      
      // SectionList: similar to FlatList
      remapProps(SectionList, {
        className: "style",
        contentContainerClassName: "contentContainerStyle",
      });
      ```
      
      ```tsx
      // Usage -- className props map to the corresponding style props
      <FlatList
        className="flex-1 bg-gray-50 dark:bg-gray-900"
        contentContainerClassName="p-4 gap-3"
        data={items}
        renderItem={renderItem}
        keyExtractor={keyExtractor}
      />
      
      <ScrollView
        className="flex-1"
        contentContainerClassName="p-4 pb-20"
      >
        {children}
      </ScrollView>
      ```
      
      **Why good:** Zero style resolution overhead, maps className strings to the component's existing style props, type-safe with declaration merging
      
      ---
      
      ## Pattern 4: cssInterop for Style-to-Prop Extraction
      
      Use `cssInterop` when a component needs style properties extracted as individual props. This has runtime cost -- use only when necessary.
      
      ```tsx
      import { TextInput, StatusBar } from "react-native";
      import { cssInterop } from "nativewind";
      
      // TextInput: extract textAlign from style, map placeholder color
      cssInterop(TextInput, {
        className: {
          target: "style",
          nativeStyleToProp: {
            textAlign: true, // Extracts textAlign from style to a prop
          },
        },
        placeholderClassName: {
          target: false, // Don't merge into any style prop
          nativeStyleToProp: {
            color: "placeholderTextColor", // Extract color -> placeholderTextColor prop
          },
        },
      });
      ```
      
      ```tsx
      // Usage -- className drives both style and extracted props
      <TextInput
        className="rounded-lg border border-gray-300 p-3 text-base text-gray-900 text-center dark:border-gray-600 dark:text-white"
        placeholderClassName="text-gray-400 dark:text-gray-500"
        placeholder="Search..."
      />
      ```
      
      **Why good:** `textAlign` extracted from style to its own prop (required by TextInput), `placeholderTextColor` derived from className instead of hardcoded color string
      
      ---
      
      ## Pattern 5: TypeScript Declarations for Third-Party Props
      
      After calling `remapProps` or `cssInterop`, add TypeScript declarations so the new props are type-safe.
      
      ```typescript
      // nativewind-env.d.ts or a dedicated declarations file
      /// <reference types="nativewind/types" />
      
      import type {
        FlatListProps,
        ScrollViewProps,
        TextInputProps,
      } from "react-native";
      
      declare module "react-native" {
        interface FlatListProps<ItemT> {
          contentContainerClassName?: string;
          columnWrapperClassName?: string;
          ListHeaderComponentClassName?: string;
          ListFooterComponentClassName?: string;
        }
      
        interface ScrollViewProps {
          contentContainerClassName?: string;
          indicatorClassName?: string;
        }
      
        // TextInput already handled by nativewind/types, but for custom mappings:
        interface TextInputProps {
          placeholderClassName?: string;
        }
      }
      ```
      
      **Why good:** TypeScript knows about the new className props, autocomplete works, type errors caught at compile time
      
      ---
      
      ## Pattern 6: Integrating SVG Components
      
      SVG libraries (react-native-svg) often need `cssInterop` because they use non-standard style props.
      
      ```tsx
      import Svg, { Circle, Path } from "react-native-svg";
      import { cssInterop } from "nativewind";
      
      // Map className to SVG-specific props
      cssInterop(Svg, {
        className: {
          target: "style",
          nativeStyleToProp: {
            width: true,
            height: true,
          },
        },
      });
      
      cssInterop(Circle, {
        className: {
          target: "style",
          nativeStyleToProp: {
            width: true,
            height: true,
            fill: "fill",
            stroke: "stroke",
            strokeWidth: "strokeWidth",
          },
        },
      });
      ```
      
      ```tsx
      // Usage
      <Svg className="h-6 w-6">
        <Circle className="fill-blue-500 stroke-blue-700" cx="12" cy="12" r="10" />
      </Svg>
      ```
      
      **Why good:** SVG dimensions and colors driven by Tailwind classes, nativeStyleToProp extracts the right attributes to SVG-specific props
      
      **When to use:** Only for SVG or similar components where style attributes must become element-specific props. For components with standard style props, prefer `remapProps`.
      
      ---
      
      ## Anti-Pattern: Using cssInterop/remapProps on Custom Components
      
      ```tsx
      // BAD -- never use cssInterop/remapProps on your own components
      import { cssInterop } from "nativewind";
      
      function MyCard({ style, children }) {
        return <View style={style}>{children}</View>;
      }
      cssInterop(MyCard, { className: "style" }); // WRONG
      
      // GOOD -- accept and merge className directly
      function MyCard({
        className,
        children,
      }: {
        className?: string;
        children: React.ReactNode;
      }) {
        return (
          <View
            className={`rounded-xl bg-white p-4 dark:bg-gray-800 ${className ?? ""}`}
          >
            {children}
          </View>
        );
      }
      ```
      
      **Why bad:** cssInterop adds runtime overhead (style resolution, event handlers, context injection) that is completely unnecessary for your own components. Your own components can accept className directly because the JSX transform handles it.
      
    • theming.md 8.7 KB
      # NativeWind - Theming Patterns
      
      > Dark mode, CSS variables, theme switching, and multi-brand theming. See [core.md](core.md) for basic className patterns.
      
      **Prerequisites**: Understand [Pattern 3: Dark Mode](../SKILL.md) and [Pattern 4: CSS Variables](../SKILL.md) from SKILL.md.
      
      ---
      
      ## Pattern 1: Dark Mode with System Preference
      
      By default, NativeWind follows the device color scheme. Apply `dark:` prefix to all conditional styles.
      
      ```tsx
      import { View, Text, ScrollView, Pressable } from "react-native";
      
      export function SettingsScreen() {
        return (
          <ScrollView className="flex-1 bg-gray-50 dark:bg-gray-900">
            <View className="p-4">
              <Text className="mb-4 text-2xl font-bold text-gray-900 dark:text-white">
                Settings
              </Text>
      
              {/* Card with proper light + dark styles */}
              <View className="rounded-xl bg-white p-4 shadow-sm dark:bg-gray-800">
                <Text className="text-base text-gray-900 dark:text-white">
                  Notifications
                </Text>
                <Text className="mt-1 text-sm text-gray-500 dark:text-gray-400">
                  Manage your notification preferences
                </Text>
              </View>
      
              {/* Divider */}
              <View className="my-4 h-px bg-gray-200 dark:bg-gray-700" />
      
              {/* Secondary action */}
              <Pressable className="rounded-lg bg-gray-100 px-4 py-3 active:bg-gray-200 dark:bg-gray-800 dark:active:bg-gray-700">
                <Text className="text-center text-gray-700 dark:text-gray-300">
                  Sign Out
                </Text>
              </Pressable>
            </View>
          </ScrollView>
        );
      }
      ```
      
      **Why good:** Every element has both light and dark variants, `active:` and `dark:active:` for press states in both modes, no conditional JS logic needed
      
      ---
      
      ## Pattern 2: Manual Theme Toggle with Persistence
      
      ```tsx
      import { useEffect, useCallback } from "react";
      import { View, Text, Pressable } from "react-native";
      import { useColorScheme } from "nativewind";
      import AsyncStorage from "@react-native-async-storage/async-storage";
      
      const THEME_STORAGE_KEY = "user-theme-preference";
      
      type ThemeOption = "light" | "dark" | "system";
      
      export function ThemeSelector() {
        const { colorScheme, setColorScheme } = useColorScheme();
      
        // Restore persisted theme on mount
        useEffect(() => {
          const restoreTheme = async () => {
            const saved = await AsyncStorage.getItem(THEME_STORAGE_KEY);
            if (saved === "light" || saved === "dark" || saved === "system") {
              setColorScheme(saved);
            }
          };
          restoreTheme();
        }, [setColorScheme]);
      
        const selectTheme = useCallback(
          async (theme: ThemeOption) => {
            setColorScheme(theme);
            await AsyncStorage.setItem(THEME_STORAGE_KEY, theme);
          },
          [setColorScheme],
        );
      
        const options: ThemeOption[] = ["light", "dark", "system"];
      
        return (
          <View className="gap-2 p-4">
            <Text className="mb-2 text-lg font-bold text-gray-900 dark:text-white">
              Appearance
            </Text>
            {options.map((option) => {
              const isActive =
                option === "system"
                  ? colorScheme === undefined
                  : option === colorScheme;
      
              return (
                <Pressable
                  key={option}
                  className={`rounded-lg px-4 py-3 ${
                    isActive
                      ? "bg-blue-500"
                      : "bg-gray-100 active:bg-gray-200 dark:bg-gray-800 dark:active:bg-gray-700"
                  }`}
                  onPress={() => selectTheme(option)}
                >
                  <Text
                    className={`text-center font-medium capitalize ${
                      isActive ? "text-white" : "text-gray-900 dark:text-white"
                    }`}
                  >
                    {option}
                  </Text>
                </Pressable>
              );
            })}
          </View>
        );
      }
      ```
      
      **Why good:** Three-way toggle (light/dark/system), persists to AsyncStorage, restores on mount, `setColorScheme("system")` returns to device preference
      
      ---
      
      ## Pattern 3: Runtime Theme Switching with vars()
      
      Use `vars()` for brand-level theming that goes beyond light/dark. CSS variables flow through React Context to all children.
      
      ```tsx
      import { View, Text, Pressable } from "react-native";
      import { vars, useColorScheme } from "nativewind";
      
      // Define theme objects as named constants
      const THEMES = {
        ocean: {
          light: vars({
            "--color-primary": "#0ea5e9",
            "--color-primary-text": "#ffffff",
            "--color-surface": "#f0f9ff",
            "--color-surface-text": "#0c4a6e",
          }),
          dark: vars({
            "--color-primary": "#38bdf8",
            "--color-primary-text": "#ffffff",
            "--color-surface": "#0c4a6e",
            "--color-surface-text": "#e0f2fe",
          }),
        },
        forest: {
          light: vars({
            "--color-primary": "#16a34a",
            "--color-primary-text": "#ffffff",
            "--color-surface": "#f0fdf4",
            "--color-surface-text": "#14532d",
          }),
          dark: vars({
            "--color-primary": "#4ade80",
            "--color-primary-text": "#ffffff",
            "--color-surface": "#14532d",
            "--color-surface-text": "#dcfce7",
          }),
        },
      } as const;
      
      type ThemeName = keyof typeof THEMES;
      
      interface ThemeProviderProps {
        theme: ThemeName;
        children: React.ReactNode;
      }
      
      export function ThemeProvider({ theme, children }: ThemeProviderProps) {
        const { colorScheme } = useColorScheme();
        const mode = colorScheme === "dark" ? "dark" : "light";
        const themeVars = THEMES[theme][mode];
      
        return (
          <View style={themeVars} className="flex-1">
            {children}
          </View>
        );
      }
      
      // Components reference CSS variables -- no prop drilling needed
      export function ThemedButton({
        label,
        onPress,
      }: {
        label: string;
        onPress: () => void;
      }) {
        return (
          <Pressable
            className="rounded-lg bg-[--color-primary] px-4 py-3 active:opacity-80"
            onPress={onPress}
          >
            <Text className="text-center font-semibold text-[--color-primary-text]">
              {label}
            </Text>
          </Pressable>
        );
      }
      
      export function ThemedCard({ title, body }: { title: string; body: string }) {
        return (
          <View className="rounded-xl bg-[--color-surface] p-4">
            <Text className="text-lg font-bold text-[--color-surface-text]">
              {title}
            </Text>
            <Text className="mt-1 text-[--color-surface-text]">{body}</Text>
          </View>
        );
      }
      ```
      
      **Why good:** Brand themes compose with light/dark mode (2x2 matrix), children reference variables without knowing the theme, switching theme re-renders only via Context change
      
      ---
      
      ## Pattern 4: Accessing CSS Variables in JavaScript
      
      Use `useUnstableNativeVariable()` when a third-party component needs a theme color as a direct prop value (not via className).
      
      ```tsx
      import { ActivityIndicator, View, Text } from "react-native";
      import { vars, useUnstableNativeVariable } from "nativewind";
      
      const theme = vars({
        "--color-primary": "#3b82f6",
        "--color-accent": "#f59e0b",
      });
      
      export function LoadingScreen() {
        return (
          <View
            style={theme}
            className="flex-1 items-center justify-center bg-white dark:bg-gray-900"
          >
            <ThemedLoader />
            <Text className="mt-4 text-[--color-primary]">Loading...</Text>
          </View>
        );
      }
      
      function ThemedLoader() {
        // ActivityIndicator.color doesn't accept className -- read variable directly
        const primaryColor = useUnstableNativeVariable("--color-primary");
        return <ActivityIndicator size="large" color={primaryColor} />;
      }
      ```
      
      **Why good:** `useUnstableNativeVariable` bridges CSS variables to props that only accept string/number values, keeps theme centralized in vars() object
      
      **Gotcha:** The `useUnstableNativeVariable` API is marked unstable and may change in future NativeWind versions. Use it sparingly -- only when a component genuinely cannot accept className for a color/value prop.
      
      ---
      
      ## Pattern 5: Tailwind Config Theme Extension
      
      Extend the default theme in `tailwind.config.js` for compile-time tokens. These are resolved at build time with zero runtime cost.
      
      ```javascript
      /** @type {import('tailwindcss').Config} */
      module.exports = {
        content: ["./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}"],
        presets: [require("nativewind/preset")],
        theme: {
          extend: {
            colors: {
              brand: {
                50: "#eff6ff",
                100: "#dbeafe",
                500: "#3b82f6",
                600: "#2563eb",
                700: "#1d4ed8",
                900: "#1e3a5a",
              },
            },
            spacing: {
              "safe-top": "env(safe-area-inset-top)",
              "safe-bottom": "env(safe-area-inset-bottom)",
            },
            borderRadius: {
              card: "12px",
            },
          },
        },
        plugins: [],
      };
      ```
      
      ```tsx
      // Usage -- custom tokens work like built-in Tailwind classes
      <View className="rounded-card bg-brand-50 p-4 dark:bg-brand-900">
        <Text className="text-brand-700 dark:text-brand-100">Branded content</Text>
      </View>
      ```
      
      **Why good:** Custom tokens resolved at compile time (zero runtime cost), consistent naming across components, safe area insets as spacing values
      
  • reference.md 6.8 KB
    # NativeWind Quick Reference
    
    > Decision frameworks, API cheat sheet, and migration notes. See [SKILL.md](SKILL.md) for red flags and anti-patterns.
    
    ---
    
    ## Decision Framework
    
    ### When to Use NativeWind
    
    ```
    Need Tailwind CSS 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)
    
    Need web + native from same codebase?
    ├─ YES → NativeWind (CSS on web, StyleSheet on native)
    └─ NO → Either NativeWind or StyleSheet.create
    ```
    
    ### Styling Third-Party Components
    
    ```
    Does the component accept a className prop already?
    ├─ YES → Use it directly (no setup needed)
    └─ NO → Does it have multiple style props (style, contentContainerStyle)?
        ├─ YES → remapProps (lightweight, maps className to style props)
        └─ NO → Does a style attribute need to become a prop?
            ├─ YES → cssInterop (extracts style attributes to props)
            │   Example: TextInput placeholderTextColor from className
            └─ NO → remapProps with simple mapping
    ```
    
    ### Dark Mode Strategy
    
    ```
    Follow system preference?
    ├─ YES → Use dark: prefix classes (automatic)
    │   └─ Expo: Ensure userInterfaceStyle: "automatic" in app.json
    └─ Need manual toggle?
        ├─ Import useColorScheme from "nativewind"
        ├─ Call toggleColorScheme() or setColorScheme("dark"|"light"|"system")
        └─ Persist choice to AsyncStorage
    ```
    
    ### Theming Strategy
    
    ```
    Static theme (compile-time)?
    ├─ YES → Customize tailwind.config.js theme.extend
    └─ NO → Need runtime theme switching?
        ├─ YES → vars() with CSS variables
        │   ├─ Define theme objects: vars({ "--color-primary": "#3b82f6" })
        │   ├─ Apply to ancestor: <View style={brandTheme}>
        │   ├─ Reference in children: className="text-[--color-primary]"
        │   └─ Read in JS: useUnstableNativeVariable("--color-primary")
        └─ Need multiple brand themes?
            └─ Combine vars() + useColorScheme for brand + light/dark matrix
    ```
    
    ---
    
    ## API Cheat Sheet
    
    ### Core APIs
    
    | API                           | Import             | Purpose                                                |
    | ----------------------------- | ------------------ | ------------------------------------------------------ |
    | `useColorScheme()`            | `nativewind`       | Read/set color scheme (light/dark/system)              |
    | `vars()`                      | `nativewind`       | Set CSS variables as style object                      |
    | `useUnstableNativeVariable()` | `nativewind`       | Read resolved CSS variable value in JS                 |
    | `cssInterop()`                | `nativewind`       | Tag third-party component for full style interop       |
    | `remapProps()`                | `nativewind`       | Map className props to style props (lightweight)       |
    | `colorScheme`                 | `nativewind`       | Module-level color scheme control (outside components) |
    | `withNativeWind()`            | `nativewind/metro` | Metro config wrapper                                   |
    
    ### useColorScheme Return Values
    
    ```typescript
    const {
      colorScheme, // "light" | "dark"
      setColorScheme, // (scheme: "light" | "dark" | "system") => void
      toggleColorScheme, // () => void -- switches between light and dark
    } = useColorScheme();
    ```
    
    ### Platform Prefixes
    
    | Prefix     | Target                   |
    | ---------- | ------------------------ |
    | `ios:`     | iOS only                 |
    | `android:` | Android only             |
    | `web:`     | Web only                 |
    | `windows:` | Windows only             |
    | `osx:`     | macOS only               |
    | `native:`  | All platforms except web |
    
    ### State Prefixes
    
    | Prefix    | Behavior                                |
    | --------- | --------------------------------------- |
    | `dark:`   | Dark color scheme active                |
    | `active:` | Component being pressed                 |
    | `hover:`  | Pointer hovering (web, pointer devices) |
    | `focus:`  | Component focused                       |
    
    ---
    
    ## Configuration Reference
    
    ### tailwind.config.js
    
    ```javascript
    /** @type {import('tailwindcss').Config} */
    module.exports = {
      content: [
        "./App.tsx",
        "./app/**/*.{js,jsx,ts,tsx}",
        "./components/**/*.{js,jsx,ts,tsx}",
        "./screens/**/*.{js,jsx,ts,tsx}",
      ],
      presets: [require("nativewind/preset")],
      theme: {
        extend: {
          // Custom values here
        },
      },
      plugins: [],
    };
    ```
    
    ### Peer Dependencies (v4)
    
    ```
    nativewind
    tailwindcss ^3.4.17
    react-native-reanimated
    react-native-safe-area-context
    ```
    
    ### TypeScript Declaration
    
    ```typescript
    // nativewind-env.d.ts (do NOT name it nativewind.d.ts)
    /// <reference types="nativewind/types" />
    ```
    
    ---
    
    ## Migration Notes
    
    ### From v2 to v4
    
    Key breaking changes:
    
    | v2                                         | v4                                       |
    | ------------------------------------------ | ---------------------------------------- |
    | `styled()` wrapper                         | Removed -- className works directly      |
    | Babel plugin approach                      | JSX import source transform              |
    | `NativeWindStyleSheet`                     | Renamed to `StyleSheet`                  |
    | `gap-` polyfill                            | Compiles to native `columnGap`/`rowGap`  |
    | rem = 16 everywhere                        | rem = 14 on native, 16 on web            |
    | `divide-` / `space-`                       | Temporarily unavailable                  |
    | className not accessible inside components | className accessible (enables clsx, cva) |
    
    ### From v4 to v5 (Preview)
    
    When NativeWind v5 stabilizes:
    
    | v4                              | v5                                        |
    | ------------------------------- | ----------------------------------------- |
    | `cssInterop()` / `remapProps()` | Unified `styled()` API                    |
    | `vars()` for theming            | `VariableContextProvider` component       |
    | Custom JSX transform            | Import rewrite system                     |
    | Tailwind CSS v3.4 config        | Tailwind CSS v4.1+ with new import syntax |
    | Requires RN 0.73+               | Requires RN 0.81+                         |
    | `platformSelect()` JS function  | CSS media queries                         |
    
    ### From StyleSheet.create to NativeWind
    
    ```tsx
    // Before: StyleSheet.create
    const styles = StyleSheet.create({
      container: { flex: 1, padding: 16, backgroundColor: "#fff" },
      title: { fontSize: 18, fontWeight: "bold", color: "#111" },
    });
    
    <View style={styles.container}>
      <Text style={styles.title}>Hello</Text>
    </View>
    
    // After: NativeWind className
    <View className="flex-1 bg-white p-4">
      <Text className="text-lg font-bold text-gray-900">Hello</Text>
    </View>
    ```
    
  • SKILL.md 19.1 KB
    ---
    name: mobile-styling-nativewind
    description: NativeWind v4+ - Tailwind CSS utility classes for React Native, className prop, CSS variables, dark mode, platform prefixes, animations, theming, third-party component integration
    ---
    
    # NativeWind Patterns
    
    > **Quick Guide:** NativeWind brings Tailwind CSS utility classes to React Native via `className` prop. Styles compile to `StyleSheet.create` at 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). Use `vars()` for runtime theming with CSS variables. Platform prefixes (`ios:`, `android:`, `native:`) replace `Platform.select` for styling. Use `remapProps` for third-party components with multiple style props; reserve `cssInterop` for components needing style-to-prop extraction.
    
    ---
    
    <critical_requirements>
    
    ## CRITICAL: Before Using This Skill
    
    > **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:**
    
    - Styling React Native components with Tailwind CSS utility classes
    - Implementing dark mode with automatic system detection or manual toggle
    - Creating dynamic themes with CSS variables via `vars()`
    - Applying platform-specific styles with `ios:`/`android:`/`native:` prefixes
    - Integrating className support with third-party React Native libraries
    - Adding transitions and animations to React Native components
    
    **Key patterns covered:**
    
    - className prop usage and custom component patterns
    - Dark mode with `useColorScheme` (system preference and manual toggle)
    - CSS variables for runtime theming via `vars()` and `useUnstableNativeVariable()`
    - Platform prefixes (`ios:`, `android:`, `web:`, `native:`) for cross-platform styling
    - Third-party component integration (`remapProps` vs `cssInterop`)
    - Animations and transitions (experimental, powered by react-native-reanimated)
    - Variant components with class merging libraries
    
    **When NOT to use:**
    
    - Web-only React projects (use standard Tailwind CSS)
    - Projects that need zero runtime overhead (use `StyleSheet.create` directly)
    - Apps on legacy React Native architecture that cannot adopt New Architecture dependencies
    
    **Detailed Resources:**
    
    - [examples/core.md](examples/core.md) - className usage, custom components, variants, conditional styling
    - [examples/theming.md](examples/theming.md) - Dark mode, CSS variables, theme switching, useColorScheme
    - [examples/platform-and-interop.md](examples/platform-and-interop.md) - Platform prefixes, cssInterop, remapProps, third-party integration
    - [reference.md](reference.md) - Decision frameworks, API cheat sheet, migration notes
    
    ---
    
    <philosophy>
    
    ## Philosophy
    
    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:**
    
    1. **Build-time compilation** -- Tailwind classes compile to native `StyleSheet.create` objects, keeping runtime overhead minimal (~2ms per render vs 0ms for raw StyleSheet)
    2. **className is first-class** -- The JSX transform makes `className` available inside your components, enabling compatibility with class merging libraries (clsx, tailwind-variants, cva)
    3. **No CSS cascade on native** -- React Native does not cascade styles. You must always declare both sides of conditional styles (`text-black dark:text-white`, not just `dark:text-white`)
    4. **Platform prefixes over Platform.select** -- For styling concerns, `ios:shadow-lg android:elevation-4` is more declarative than wrapping in `Platform.select`
    5. **Custom components just merge classNames** -- Never use `cssInterop` or `remapProps` on your own components. Simply accept a `className` prop and merge it with defaults
    6. **Third-party integration is explicit** -- Use `remapProps` (lightweight) or `cssInterop` (full runtime) only for third-party components that need className support
    
    **Architecture:**
    
    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)
    - Inline `style` props merge with className-based styles, with inline taking precedence
    
    **rem 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.
    
    </philosophy>
    
    ---
    
    <patterns>
    
    ## Core Patterns
    
    ### Pattern 1: className on React Native Components
    
    All React Native core components accept `className` after installing NativeWind. Styles compile at build time -- no runtime string parsing in production.
    
    ```tsx
    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](examples/core.md) for custom component patterns with className merging and variant props.
    
    ---
    
    ### Pattern 2: Custom Components with className Merging
    
    Accept a `className` prop and merge it with defaults. Never use `cssInterop` or `remapProps` on your own components.
    
    ```tsx
    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](examples/core.md) for patterns with clsx and tailwind-variants.
    
    ---
    
    ### Pattern 3: Dark Mode with useColorScheme
    
    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.
    
    ```tsx
    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](examples/theming.md) for manual theme persistence and multi-theme patterns with `vars()`.
    
    ---
    
    ### Pattern 4: CSS Variables for Runtime Theming
    
    Use `vars()` to set CSS variable values that flow down the component tree via React Context. Use `useUnstableNativeVariable()` to read resolved values in JavaScript.
    
    ```tsx
    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](examples/theming.md) for multi-brand theming and combining `vars()` with `useColorScheme`.
    
    ---
    
    ### Pattern 5: Platform Prefixes
    
    Use `ios:`, `android:`, `web:`, and `native:` prefixes to apply styles per platform. The `native:` prefix targets all platforms except web.
    
    ```tsx
    <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](examples/platform-and-interop.md) for complex platform patterns.
    
    ---
    
    ### Pattern 6: Third-Party Component Integration
    
    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.
    
    ```tsx
    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](examples/platform-and-interop.md) for TypeScript declarations, SVG integration, and the decision framework.
    
    ---
    
    ### Pattern 7: Animations and Transitions (Experimental)
    
    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.
    
    ```tsx
    // 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.
    
    </patterns>
    
    ---
    
    <decision_framework>
    
    ## Decision Framework
    
    ### Styling Approach
    
    ```
    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)
    ```
    
    ### Third-Party Component Integration
    
    ```
    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
    ```
    
    ### Theming Strategy
    
    ```
    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](reference.md) for full API cheat sheet, dark mode strategy tree, and migration notes.
    
    </decision_framework>
    
    ---
    
    <red_flags>
    
    ## RED FLAGS
    
    **High Priority Issues:**
    
    - Declaring only dark styles without light counterpart (`dark:text-white` without `text-black`) -- React Native has no CSS cascade, so the light variant will have no text color
    - Using `cssInterop` or `remapProps` on your own custom components -- these are exclusively for third-party components. Your own components should accept and merge `className` directly
    - Missing `import "./global.css"` at app entry point -- no styles will render without it
    - Missing `nativewind/preset` in tailwind.config.js presets -- platform prefixes, CSS variable support, and other NativeWind-specific features will not work
    - Using `useColorScheme` from `react-native` instead of `nativewind` -- the nativewind version provides `setColorScheme` and `toggleColorScheme`
    
    **Medium Priority Issues:**
    
    - Using `cssInterop` when `remapProps` would suffice -- `cssInterop` has runtime overhead for style resolution, event handlers, and context injection
    - Naming the TypeScript declaration file `nativewind.d.ts` -- it conflicts with the package's own types. Use `nativewind-env.d.ts`
    - Not setting `userInterfaceStyle: "automatic"` in Expo app.json -- system dark mode preference will not be detected
    - Using web-designed breakpoints (`sm:`, `md:`, `lg:`) without customizing for mobile -- NativeWind's default breakpoints are web-centric (640px, 768px, 1024px) and may not match mobile screen sizes
    
    **Gotchas & Edge Cases:**
    
    - Inline `style` prop takes precedence over `className` styles due to CSS specificity -- `<Text className="text-white" style={{ color: "black" }} />` renders black
    - `rem` units differ between platforms: 14 on native (RN default font size), 16 on web -- use px values in theme config for consistency
    - Color opacity is disabled by default for performance on native -- enable via `corePlugins` in tailwind.config.js if you need `bg-blue-500/50` syntax
    - `vars()` values propagate via React Context, not actual CSS -- they only flow to React children, not portal-rendered content
    - `useUnstableNativeVariable` API may change in future versions (prefixed "unstable" intentionally)
    - Animations and transitions are experimental on native -- `transition-shadow` is web-only, and animation performance is actively being improved
    - `gap-` compiles to native `columnGap`/`rowGap` in v4 (v2 used a polyfill) -- verify your React Native version supports gap layout props
    - `divide-` and `space-` utilities are temporarily unavailable in v4
    - NativeWind v5 (in preview) deprecates `cssInterop`/`remapProps` in favor of `styled()`, and `vars()` in favor of `VariableContextProvider` -- check migration guide when upgrading
    - Tailwind CSS v4 is NOT yet supported by NativeWind v4 -- NativeWind v4 uses Tailwind CSS v3.4 config format
    
    </red_flags>
    
    ---
    
    <critical_reminders>
    
    ## 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>
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related