Skip to Content
ConventionsStyling Rules

Styling Rules

RNCopilot uses react-native-unistyles  3.x for all styling. This page covers the three fundamental laws, the variants API, responsive helpers, and how to use theme tokens.

The Three Laws

These are non-negotiable. Every style in the codebase follows them.

  1. Never use inline styles. All styles go in StyleSheet.create.
  2. Never use color literals. All colors come from theme.colors.*.
  3. Never hardcode numeric spacing. Use theme.metrics.spacing.* or the responsive helpers.
// WRONG -- all three laws violated <View style={{ padding: 16, backgroundColor: '#fff' }}> // CORRECT <View style={styles.container}>

StyleSheet Pattern

Import StyleSheet from react-native-unistyles (not from react-native). The callback receives the active theme, giving you full access to tokens:

import { StyleSheet } from 'react-native-unistyles'; const styles = StyleSheet.create((theme) => ({ container: { padding: theme.metrics.spacing.p16, backgroundColor: theme.colors.background.surface, borderRadius: theme.metrics.borderRadius.md, }, }));

Separate Style Files

When styles are substantial, put them in a dedicated .styles.ts file:

// Button.styles.ts import { StyleSheet } from 'react-native-unistyles'; export const styles = StyleSheet.create((theme) => ({ container: { borderRadius: theme.metrics.borderRadius.lg, alignItems: 'center', justifyContent: 'center', }, }));

Variants API

Use the unistyles variants API for any style that changes based on props. Define variants declaratively inside the style object:

// Component.styles.ts import { StyleSheet, type UnistylesVariants } from 'react-native-unistyles'; export const styles = StyleSheet.create((theme) => ({ container: { borderRadius: theme.metrics.borderRadius.lg, variants: { variant: { primary: { backgroundColor: theme.colors.brand.primary }, secondary: { backgroundColor: theme.colors.background.surfaceAlt }, outline: { backgroundColor: 'transparent', borderWidth: 1, borderColor: theme.colors.border.default, }, }, size: { sm: { padding: theme.metrics.spacing.p8 }, md: { padding: theme.metrics.spacing.p12 }, lg: { padding: theme.metrics.spacing.p16 }, }, disabled: { true: { opacity: 0.5 }, }, }, }, })); export type MyComponentStyleVariants = UnistylesVariants<typeof styles>;

In the component, call useVariants once before accessing any styles:

// Component.tsx import { styles } from './Component.styles'; export function MyComponent({ variant, size, disabled }: MyComponentProps) { styles.useVariants({ variant, size, disabled }); return <View style={styles.container} />; }

Call styles.useVariants(...) once per render, before accessing any style property. Do not call it conditionally or inside loops.

Key Variant Rules

  • Use compoundVariants for styles that depend on multiple variant combinations.
  • Use boolean variants (true/false keys) for toggleable states like disabled, focused, error.
  • Use breakpoint-responsive values for tablet adaptation:
padding: { xs: theme.metrics.spacing.p12, md: theme.metrics.spacing.p24 }

miniRuntime

The StyleSheet.create callback receives a second argument (rt) for safe area insets and screen dimensions:

const styles = StyleSheet.create((theme, rt) => ({ container: { paddingTop: rt.insets.top, paddingBottom: rt.insets.bottom, }, }));

Responsive Helpers

Import from @/theme/metrics:

import { rf, hs, vs } from '@/theme/metrics';
HelperPurposeScales With
rf(n)Responsive font sizeScreen width (base: 390)
hs(n)Horizontal scaleScreen width (base: 390)
vs(n)Vertical scaleScreen height (base: 844)

Use these in StyleSheet.create for dynamic values not available as theme tokens:

const styles = StyleSheet.create((theme) => ({ customElement: { width: hs(120), height: vs(48), fontSize: rf(14), }, }));

The theme already uses hs(), vs(), and rf() internally for all spacing, font size, and border radius tokens. Prefer using theme.metrics.* tokens when a matching value exists.

Theme Token Quick Reference

For a full listing of every token and its value, see the Theme Tokens reference page. Here is a summary of the categories available on the theme object:

theme.colors.brand.* brand colors (primary, secondary, tertiary, variants) theme.colors.background.* surface colors (app, surface, surfaceAlt, elevated, ...) theme.colors.text.* text colors (primary, secondary, inverse, link, ...) theme.colors.border.* border colors (default, subtle, strong, focus, ...) theme.colors.icon.* icon colors theme.colors.state.* semantic colors (success, warning, error, info + backgrounds) theme.colors.overlay.* overlay and shadow colors theme.colors.gradient.* gradient arrays theme.colors.shadow.* shadow properties theme.colors.mode 'light' | 'dark' theme.metrics.spacing.* horizontal spacing (p4 through p120, scaled via hs()) theme.metrics.spacingV.* vertical spacing (p4 through p120, scaled via vs()) theme.metrics.fontSize.* font sizes (xxs through 6xl, scaled via rf()) theme.metrics.borderRadius.* border radii (xs, sm, md, lg, xl, full) theme.metrics.iconSize.* icon sizes (xs, sm, md, lg, xl)
Last updated on