Marketplace rn-styling
Styling patterns for React Native with NativeWind and BrandColors. Use when working with styles, themes, colors, responsive layouts, or platform-specific UI in Expo/React Native.
git clone https://github.com/aiskillstore/marketplace
T=$(mktemp -d) && git clone --depth=1 https://github.com/aiskillstore/marketplace "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/cjharmath/rn-styling" ~/.claude/skills/aiskillstore-marketplace-rn-styling && rm -rf "$T"
skills/cjharmath/rn-styling/SKILL.mdReact Native Styling
Problem Statement
React Native styling differs fundamentally from web CSS. NativeWind bridges the gap but has its own rules. This codebase uses a hybrid approach: BrandColors for semantic colors, NativeWind for layout utilities.
Pattern: BrandColors vs NativeWind Classes
Rule: Use BrandColors for semantic colors, NativeWind for layout/spacing.
// ✅ CORRECT: Hybrid approach <View className="flex-1 p-4 rounded-lg" style={{ backgroundColor: BrandColors.background }}> <Text className="text-lg font-semibold" style={{ color: BrandColors.textPrimary }}> Title </Text> </View> // ❌ WRONG: Hardcoded hex colors (violation scanner blocks this) <View className="flex-1 p-4 bg-[#1a1a2e]"> // ❌ WRONG: NativeWind color classes for brand colors <View className="flex-1 p-4 bg-blue-500"> // ✅ ACCEPTABLE: NativeWind brand aliases (if configured) <View className="flex-1 p-4 bg-brand-blue">
When to use which:
| Use Case | Approach |
|---|---|
| Brand colors (primary, secondary) | |
| Background colors | |
| Text colors | , |
| Layout (flex, padding, margin) | NativeWind classes |
| Borders, radius | NativeWind classes |
| Shadows | Style object (NativeWind shadows limited on iOS) |
Pattern: Theme-Aware Colors
Problem: Supporting light/dark mode with BrandColors.
// BrandColors.ts exports both themes import { BrandColors, BrandColorsDark } from '@/constants/BrandColors'; // Hook for current theme colors import { useColorScheme } from 'react-native'; function useThemeColors() { const colorScheme = useColorScheme(); return colorScheme === 'dark' ? BrandColorsDark : BrandColors; } // Component usage function ThemedCard({ title }: { title: string }) { const colors = useThemeColors(); return ( <View className="p-4 rounded-lg" style={{ backgroundColor: colors.cardBackground }} > <Text style={{ color: colors.textPrimary }}>{title}</Text> </View> ); }
Pattern: NativeWind Class Ordering
Problem: Unlike web CSS, React Native doesn't cascade. Last class wins for conflicting properties.
// Class order matters! <View className="p-4 p-2" /> // p-2 wins (last) <View className="p-2 p-4" /> // p-4 wins (last) // Conditional classes - be explicit <View className={`p-4 ${isCompact ? 'p-2' : ''}`} /> // If isCompact: "p-4 p-2" → p-2 wins ✅ // Merging className props interface Props { className?: string; } function Card({ className }: Props) { // Parent classes override defaults (they come last) return <View className={`p-4 rounded-lg ${className ?? ''}`} />; } // Usage: <Card className="p-8" /> → p-8 wins over p-4
Pattern: Platform-Specific Styles
import { Platform, StyleSheet } from 'react-native'; // Option 1: Platform.select const styles = StyleSheet.create({ shadow: Platform.select({ ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, }, android: { elevation: 4, }, }), }); // Option 2: Platform.OS check <View style={Platform.OS === 'ios' ? styles.iosShadow : styles.androidShadow} /> // Option 3: NativeWind platform prefixes <View className="ios:pt-12 android:pt-8" />
Pattern: Safe Area Handling
import { SafeAreaView } from 'react-native-safe-area-context'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; // Option 1: SafeAreaView wrapper (simplest) function Screen() { return ( <SafeAreaView className="flex-1" edges={['top', 'bottom']}> <Content /> </SafeAreaView> ); } // Option 2: Manual insets (more control) function Screen() { const insets = useSafeAreaInsets(); return ( <View className="flex-1" style={{ paddingTop: insets.top, paddingBottom: insets.bottom }} > <Content /> </View> ); } // Option 3: NativeWind safe area utilities (if configured) <View className="flex-1 pt-safe pb-safe">
Pattern: Keyboard Avoiding
import { KeyboardAvoidingView, Platform } from 'react-native'; function FormScreen() { return ( <KeyboardAvoidingView className="flex-1" behavior={Platform.OS === 'ios' ? 'padding' : 'height'} keyboardVerticalOffset={Platform.OS === 'ios' ? 64 : 0} // Adjust for header > <ScrollView className="flex-1"> <TextInput /> <TextInput /> <SubmitButton /> </ScrollView> </KeyboardAvoidingView> ); }
Pattern: Responsive Breakpoints
Note: NativeWind v2 breakpoints differ from web Tailwind.
// NativeWind v2 breakpoints (based on window width) // sm: 640px, md: 768px, lg: 1024px, xl: 1280px // Responsive padding <View className="p-2 sm:p-4 md:p-6" /> // Responsive flex direction <View className="flex-col sm:flex-row" /> // Check screen size programmatically import { useWindowDimensions } from 'react-native'; function ResponsiveLayout() { const { width } = useWindowDimensions(); const isTablet = width >= 768; return isTablet ? <TabletLayout /> : <PhoneLayout />; }
Pattern: Animated Styles
Problem: Avoiding re-renders with Animated values.
import { Animated } from 'react-native'; function FadeInCard() { // useRef to persist Animated.Value across renders const fadeAnim = useRef(new Animated.Value(0)).current; useEffect(() => { Animated.timing(fadeAnim, { toValue: 1, duration: 300, useNativeDriver: true, // Always use when animating opacity/transform }).start(); }, []); return ( <Animated.View className="p-4 rounded-lg" style={[ { backgroundColor: BrandColors.cardBackground }, { opacity: fadeAnim }, // Animated style in array ]} > <Text>Content</Text> </Animated.View> ); }
Style arrays: Combine static + animated styles.
// ✅ CORRECT: Style array style={[styles.card, { opacity: fadeAnim }]} // ❌ WRONG: Spread (creates new object each render) style={{ ...styles.card, opacity: fadeAnim }}
Pattern: StyleSheet vs Inline
// Use StyleSheet for: // - Complex styles reused across renders // - Styles with many properties // - Performance-critical components const styles = StyleSheet.create({ card: { padding: 16, borderRadius: 12, backgroundColor: BrandColors.cardBackground, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, }, }); // Use inline/NativeWind for: // - Simple layout utilities // - One-off styles // - Conditional styles <View className="flex-1 p-4" /> <View style={{ marginTop: dynamicValue }} />
BrandColors Pattern
Create a centralized color constants file:
// constants/BrandColors.ts export const BrandColors = { primary: '#...', secondary: '#...', background: '#...', cardBackground: '#...', textPrimary: '#...', textSecondary: '#...', // ... etc }; export const BrandColorsDark = { // Dark mode variants };
Recommended: Violation Scanner
Consider adding a violation scanner to block:
- Hardcoded hex colors (except allowed exceptions)
- Direct color strings
NativeWind Notes
If using NativeWind v2 (not v4), note these differences:
prop on RN componentsclassName- Limited web Tailwind parity
- Some utilities unsupported
Common Issues
| Issue | Solution |
|---|---|
| Color not applying | Check BrandColors import, verify theme context |
| NativeWind class ignored | Not all Tailwind utilities work - check v2 docs |
| Shadow not showing (iOS) | Use StyleSheet with shadowColor/Offset/Opacity/Radius |
| Shadow not showing (Android) | Use property |
| Safe area not respected | Wrap in SafeAreaView or use insets |
| Style flicker on mount | Use Animated for transitions |
Recommended File Structure
constants/ BrandColors.ts # Color definitions designSystem.ts # Spacing, typography scales components/ ui/Card.tsx # Example hybrid styling app/ _layout.tsx # Theme provider setup