UTA DevHub
Guides

Styling Implementation Guide

Guidelines for implementing styles using StyleSheet and Unistyles.

Styling Implementation Guide

Overview

This guide details the patterns and best practices for styling components in our React Native application. We primarily use React Native StyleSheet for core styling and Unistyles for advanced features like theming, responsive styles, and performance optimization.

When To Use

  • StyleSheet.create: For standard, static component styles.
  • Unistyles (createStyleSheet, useStyles):
    • When styles need to adapt based on theme (light/dark mode).
    • For responsive styles that change based on screen size or orientation.
    • When accessing theme variables (colors, spacing, fonts) directly within styles.
    • For performance-critical components where Unistyles' optimizations are beneficial.

Implementation Patterns

1. Basic Styling with StyleSheet.create

Use StyleSheet.create for simple, non-dynamic styles. This is the most performant method for static styles.

// src/ui/components/BasicButton/styles.ts
import { StyleSheet } from 'react-native';
 
export const styles = StyleSheet.create({
  container: {
    paddingVertical: 12,
    paddingHorizontal: 24,
    borderRadius: 8,
    backgroundColor: '#007AFF', // Example static color
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    color: '#FFFFFF',
    fontSize: 16,
    fontWeight: '600',
  },
  disabledContainer: {
    backgroundColor: '#B0B0B0',
  },
});
// src/ui/components/BasicButton/BasicButton.tsx
import React from 'react';
import { TouchableOpacity, Text, TouchableOpacityProps } from 'react-native';
import { styles } from './styles';
 
interface BasicButtonProps extends TouchableOpacityProps {
  title: string;
}
 
export const BasicButton: React.FC<BasicButtonProps> = ({ title, style, disabled, ...props }) => {
  return (
    <TouchableOpacity
      style={[
        styles.container,
        disabled && styles.disabledContainer,
        style, // Allow consuming components to override styles
      ]}
      disabled={disabled}
      {...props}
    >
      <Text style={styles.text}>{title}</Text>
    </TouchableOpacity>
  );
};

2. Theming and Dynamic Styles with Unistyles

Define themes and breakpoints, then create stylesheets that adapt.

a) Define Themes and Breakpoints:

// src/core/styles/theme.ts
export const lightTheme = {
    colors: {
        background: '#FFFFFF',
        text: '#111111',
        primary: '#007AFF',
        card: '#F5F5F5',
        border: '#DDDDDD',
    },
    spacing: {
        sm: 4,
        md: 8,
        lg: 16,
        xl: 24,
    },
    // ... other theme variables (typography, radii)
} as const // Use 'as const' for stricter typing
 
export const darkTheme = {
    colors: {
        background: '#121212',
        text: '#FFFFFF',
        primary: '#0A84FF',
        card: '#1E1E1E',
        border: '#333333',
    },
    spacing: {
        sm: 4,
        md: 8,
        lg: 16,
        xl: 24,
    },
    // ... other theme variables
} as const
 
// Define breakpoints for responsive design
export const breakpoints = {
    xs: 0,
    sm: 576,
    md: 768,
    lg: 992,
    xl: 1200,
} as const
 
// Type definition for themes
type AppTheme = typeof lightTheme
 
// Extend Unistyles registry
declare module 'react-native-unistyles' {
    export interface UnistylesThemes extends AppTheme {}
    export interface UnistylesBreakpoints extends typeof breakpoints {}
}

b) Register Unistyles:

// src/core/styles/unistyles.ts (or in your App.tsx setup)
import { UnistylesRegistry } from 'react-native-unistyles'
import { lightTheme, darkTheme, breakpoints } from './theme'
 
UnistylesRegistry
    .addThemes({ light: lightTheme, dark: darkTheme })
    .addBreakpoints(breakpoints)
    .addConfig({
        // Optionally set initial theme
        // initialTheme: 'light',
        adaptiveThemes: true, // Enable automatic theme switching based on system
    })

c) Create a Unistyles Stylesheet:

// src/ui/components/ThemedCard/styles.ts
import { createStyleSheet } from 'react-native-unistyles'
 
export const stylesheet = createStyleSheet((theme, runtime) => ({
  container: {
    backgroundColor: theme.colors.card,
    borderRadius: theme.spacing.md,
    padding: theme.spacing.lg,
    borderWidth: 1,
    borderColor: theme.colors.border,
    shadowColor: theme.colors.text, // Shadow color might differ significantly
    shadowOffset: {
      width: 0,
      // Responsive shadow based on screen height
      height: runtime.screen.height > 800 ? 4 : 2, 
    },
    shadowOpacity: theme.name === 'dark' ? 0.3 : 0.1, // Different opacity for dark mode
    shadowRadius: theme.spacing.sm,
    elevation: 3,
    // Responsive padding using breakpoints
    paddingHorizontal: {
        xs: theme.spacing.md,
        md: theme.spacing.lg, // Apply larger padding on medium screens and up
    }
  },
  title: {
    fontSize: 18,
    fontWeight: 'bold',
    color: theme.colors.primary,
    marginBottom: theme.spacing.sm,
  },
  content: {
    fontSize: 14,
    color: theme.colors.text,
    // Different line height based on breakpoint
    lineHeight: {
        xs: 18,
        lg: 20
    }
  },
}));

d) Use the Stylesheet in a Component:

// src/ui/components/ThemedCard/ThemedCard.tsx
import React from 'react';
import { View, Text, ViewProps } from 'react-native';
import { useStyles } from 'react-native-unistyles';
import { stylesheet } from './styles'; // Import the unistyles sheet
 
interface ThemedCardProps extends ViewProps {
  title?: string;
  children: React.ReactNode;
}
 
export const ThemedCard: React.FC<ThemedCardProps> = ({ title, children, style }) => {
  // Use the useStyles hook to get themed styles
  const { styles } = useStyles(stylesheet);
 
  return (
    <View style={[styles.container, style]}> // Apply themed styles
      {title && <Text style={styles.title}>{title}</Text>}
      <View>
        {typeof children === 'string' ? (
           <Text style={styles.content}>{children}</Text>
        ) : (
            children
        )}
      </View>
    </View>
  );
};

3. Combining StyleSheet and Unistyles

You can mix both approaches. Use StyleSheet for static parts and useStyles for theme-dependent parts.

// styles.ts
import { StyleSheet } from 'react-native';
import { createStyleSheet as createUnistylesSheet } from 'react-native-unistyles';
 
// Static styles
export const staticStyles = StyleSheet.create({
    icon: {
        width: 24,
        height: 24,
        marginRight: 8,
    },
    // ... other static styles
});
 
// Dynamic/Themed styles
export const dynamicStylesheet = createUnistylesSheet((theme) => ({
    container: {
        flexDirection: 'row',
        alignItems: 'center',
        padding: theme.spacing.md,
        backgroundColor: theme.colors.background,
    },
    text: {
        color: theme.colors.text,
        fontSize: 16,
    },
}));
// Component.tsx
import React from 'react';
import { View, Text } from 'react-native';
import { useStyles } from 'react-native-unistyles';
import { staticStyles, dynamicStylesheet } from './styles';
import Icon from 'react-native-vector-icons/MaterialIcons'; // Example icon
 
export const MixedStyleComponent = () => {
    const { styles: dynamicStyles } = useStyles(dynamicStylesheet);
 
    return (
        <View style={dynamicStyles.container}> 
            <Icon name="info" size={24} style={staticStyles.icon} color={dynamicStyles.text.color} />
            <Text style={dynamicStyles.text}>Styled Text</Text>
        </View>
    );
}

4. Responsive Styles (Breakpoints)

Unistyles allows defining styles specific to breakpoints.

// styles.ts
import { createStyleSheet } from 'react-native-unistyles'
 
export const stylesheet = createStyleSheet((theme, runtime) => ({
    container: {
        flexDirection: {
            xs: 'column', // Column layout on small screens
            md: 'row'    // Row layout on medium screens and up
        },
        padding: theme.spacing.md,
        alignItems: 'center'
    },
    image: {
        width: '100%', // Full width on small screens
        height: 150,
        marginBottom: {
            xs: theme.spacing.md,
            md: 0 // No margin bottom on larger screens (row layout)
        },
        marginRight: {
            xs: 0,
            md: theme.spacing.lg // Add margin right for row layout
        },
        // Specific width on larger screens
        width: {
            md: 150
        }
    },
    textContainer: {
        flex: 1 // Take remaining space in row layout
    },
    title: {
        fontSize: {
            xs: 18,
            lg: 22 // Larger font on large screens
        },
        fontWeight: 'bold',
        color: theme.colors.primary
    }
}));

5. Global Styles and Style Utilities

Define reusable style patterns and utilities in the core/styles directory.

// src/core/styles/typography.ts
import { TextStyle } from 'react-native';
 
export const typography = {
    h1: {
        fontSize: 32,
        fontWeight: 'bold',
    } as TextStyle,
    h2: {
        fontSize: 24,
        fontWeight: 'bold',
    } as TextStyle,
    body: {
        fontSize: 16,
    } as TextStyle,
    caption: {
        fontSize: 12,
        color: '#666666',
    } as TextStyle,
};
 
// src/core/styles/layout.ts
export const layout = {
    center: {
        alignItems: 'center',
        justifyContent: 'center',
    },
    row: {
        flexDirection: 'row',
    },
    // ... other layout utilities
};
 
// src/core/styles/index.ts
export * from './theme';
export * from './typography';
export * from './layout';
export * from './unistyles'; // Assuming Unistyles setup is here or imported
// Usage in a component's stylesheet
import { createStyleSheet } from 'react-native-unistyles';
import { typography, layout } from '@/core/styles'; // Import global styles
 
export const stylesheet = createStyleSheet((theme) => ({
    container: {
        ...layout.center, // Spread layout utility
        padding: theme.spacing.lg,
    },
    title: {
        ...typography.h1, // Spread typography style
        color: theme.colors.primary,
        marginBottom: theme.spacing.md,
    },
}));

Common Challenges

  • Over-reliance on Inline Styles: While convenient for quick adjustments, excessive inline styles (style={{ color: 'red' }}) hurt performance and maintainability. Prefer StyleSheet.create or Unistyles.
  • Performance with Dynamic Styles: Creating new style objects in the render function (style={{ backgroundColor: props.isActive ? 'blue' : 'grey' }}) can cause unnecessary re-renders. Use StyleSheet.flatten with conditional styles from StyleSheet.create or leverage Unistyles variants/dynamic functions.
  • Specificity and Overrides: Understanding how styles cascade and override each other is important. Styles passed via the style prop typically override styles defined within the component.
  • Platform Differences: Some style properties behave differently or are unavailable on iOS vs. Android (e.g., elevation vs. shadow*). Test on both platforms or use platform-specific code (Platform.select) when necessary.

Performance Considerations

  • (Do ✅) Use StyleSheet.create for static styles. React Native optimizes these by sending them over the bridge only once.
  • (Do ✅) Use Unistyles for themed and responsive styles. It's highly optimized for dynamic style generation.
  • (Don't ❌) Use Inline Styles (style={{...}}) frequently, especially within loops or frequently re-rendering components, as it creates new objects on every render.
  • (Don't ❌) Create Style Objects in Render: Avoid defining style objects directly within the component function body. Define them outside using StyleSheet.create or createStyleSheet from Unistyles.
  • (Consider 🤔) StyleSheet.flatten: Use StyleSheet.flatten([styles.base, conditionalStyle]) when conditionally applying styles from StyleSheet.create, but be aware it has a minor performance cost compared to purely static styles.

Examples

Themed Button using Unistyles

// src/ui/components/ThemedButton/styles.ts
import { createStyleSheet } from 'react-native-unistyles'
 
export const stylesheet = createStyleSheet((theme) => ({
    container: {
        paddingVertical: theme.spacing.md,
        paddingHorizontal: theme.spacing.lg,
        borderRadius: 8,
        backgroundColor: theme.colors.primary,
        alignItems: 'center',
        opacity: 1, // Default opacity
    },
    text: {
        color: theme.colors.background, // Text color contrasts with primary
        fontSize: 16,
        fontWeight: 'bold',
    },
    // Variant for disabled state
    disabled: {
        backgroundColor: theme.colors.border, // Use a disabled color from theme
        opacity: 0.6,
    },
    // Variant for secondary style
    secondary: {
        backgroundColor: theme.colors.card,
        borderWidth: 1,
        borderColor: theme.colors.primary,
    },
    secondaryText: {
        color: theme.colors.primary,
    },
}));
// src/ui/components/ThemedButton/ThemedButton.tsx
import React from 'react';
import { TouchableOpacity, Text, TouchableOpacityProps } from 'react-native';
import { useStyles } from 'react-native-unistyles';
import { stylesheet } from './styles';
 
interface ThemedButtonProps extends TouchableOpacityProps {
  title: string;
  variant?: 'primary' | 'secondary';
}
 
export const ThemedButton: React.FC<ThemedButtonProps> = ({ title, style, disabled, variant = 'primary', ...props }) => {
  const { styles } = useStyles(stylesheet);
 
  const isSecondary = variant === 'secondary';
 
  return (
    <TouchableOpacity
      style={[
        styles.container,
        isSecondary && styles.secondary, // Apply secondary container style
        disabled && styles.disabled, // Apply disabled style
        style,
      ]}
      disabled={disabled}
      {...props}
    >
      <Text style={[styles.text, isSecondary && styles.secondaryText]}>
        {title}
      </Text>
    </TouchableOpacity>
  );
};
  • DOC-01: Core Architecture Reference
  • DOC-02: Project Structure Reference