Skip to Content
Core ConceptsTheme System

Theme System

RNCopilot uses a semantic token-based theme system powered by react-native-unistyles 3.x. Every color, spacing value, font size, and border radius is defined as a theme token — never hardcoded in component styles.

Color Palette

The theme is built around two primary colors:

RoleLight ModeDark ModeUsage
PrimaryIndigo #6366F1Bright Indigo #818CF8Buttons, links, active states
AccentTeal #14B8A6Bright Teal #2DD4BFHighlights, badges, accent text
SecondaryDark Slate #1E293BLight Slate #CBD5E1Headings, secondary icons

Light and Dark Modes

Both themes share the same token structure (ThemeColors), ensuring that any component styled with theme tokens automatically adapts to the active mode.

// src/theme/light-theme.ts (excerpt) export const lightColors: ThemeColors = { mode: 'light', brand: { primary: '#6366F1', // Indigo secondary: '#1E293B', // Dark Slate tertiary: '#14B8A6', // Teal primaryVariant: '#4F46E5', secondaryVariant: '#334155', }, background: { app: '#F8FAFC', surface: '#FFFFFF', surfaceAlt: '#F1F5F9', // ... }, // ... };

Token Categories

The ThemeColors interface is organized into nine semantic categories:

brand — Brand Identity

theme.colors.brand.primary -- Main brand color (Indigo) theme.colors.brand.secondary -- Secondary brand color (Slate) theme.colors.brand.tertiary -- Accent color (Teal) theme.colors.brand.primaryVariant -- Darker/lighter variant of primary theme.colors.brand.secondaryVariant -- Darker/lighter variant of secondary

background — Surface Colors

theme.colors.background.app -- Page/screen background theme.colors.background.surface -- Cards, sheets, panels theme.colors.background.surfaceAlt -- Alternate surface (e.g., striped rows) theme.colors.background.section -- Section backgrounds theme.colors.background.elevated -- Elevated elements (floating cards) theme.colors.background.input -- Input field backgrounds theme.colors.background.disabled -- Disabled element backgrounds theme.colors.background.modal -- Modal/dialog backgrounds

text — Typography

theme.colors.text.primary -- Headings, body text theme.colors.text.secondary -- Supporting/subtitle text theme.colors.text.tertiary -- Metadata, timestamps theme.colors.text.muted -- Placeholders, hints theme.colors.text.inverse -- Text on primary/dark backgrounds theme.colors.text.accent -- Teal accent text theme.colors.text.link -- Link text theme.colors.text.linkHover -- Link hover state

border — Borders and Dividers

theme.colors.border.default -- Standard borders theme.colors.border.subtle -- Very light borders theme.colors.border.strong -- Prominent borders theme.colors.border.focus -- Input focus rings theme.colors.border.disabled -- Disabled element borders

icon — Iconography

theme.colors.icon.primary -- Primary icons (brand color) theme.colors.icon.secondary -- Secondary icons (dark/light) theme.colors.icon.tertiary -- Subtle icons theme.colors.icon.muted -- Very subtle icons theme.colors.icon.inverse -- Icons on dark backgrounds theme.colors.icon.accent -- Teal accent icons

state — Semantic States

theme.colors.state.success -- Success foreground (#10B981) theme.colors.state.successBg -- Success background (#ECFDF5) theme.colors.state.warning -- Warning foreground (#F59E0B) theme.colors.state.warningBg -- Warning background theme.colors.state.error -- Error foreground (#EF4444) theme.colors.state.errorBg -- Error background theme.colors.state.info -- Info foreground (#3B82F6) theme.colors.state.infoBg -- Info background theme.colors.state.disabled -- Disabled foreground

overlay — Overlays and Effects

theme.colors.overlay.modal -- Modal backdrop theme.colors.overlay.pressed -- Press feedback theme.colors.overlay.hover -- Hover feedback theme.colors.overlay.focus -- Focus ring overlay theme.colors.overlay.ripple -- Ripple effect theme.colors.overlay.shadow -- Shadow color

gradient — Gradient Pairs

theme.colors.gradient.primary -- [start, end] primary gradient theme.colors.gradient.secondary -- [start, end] secondary gradient theme.colors.gradient.accent -- [start, end] teal accent gradient theme.colors.gradient.success -- [start, end] green success gradient theme.colors.gradient.highlight -- [start, end] purple highlight gradient

shadow — Shadow Configuration

theme.colors.shadow.color -- Shadow color theme.colors.shadow.elevation -- Default elevation theme.colors.shadow.elevationSmall -- Small shadow theme.colors.shadow.elevationMedium -- Medium shadow theme.colors.shadow.elevationLarge -- Large shadow

Checking the Current Mode

Use theme.colors.mode to conditionally render based on the active theme:

const styles = StyleSheet.create((theme) => ({ container: { // theme.colors.mode is 'light' or 'dark' borderWidth: theme.colors.mode === 'dark' ? 0 : 1, }, }));

In a component, access the current theme via useUnistyles:

import { useUnistyles } from 'react-native-unistyles'; function MyComponent() { const { theme } = useUnistyles(); const isDark = theme.colors.mode === 'dark'; return ( <Icon name={isDark ? 'moon' : 'sun'} color={theme.colors.icon.primary} /> ); }

Toggling Themes at Runtime

import { toggleDarkMode } from '@/theme/themeManager'; // Switch to dark mode (persisted to MMKV) toggleDarkMode(true); // Switch to light mode toggleDarkMode(false);

The preference is persisted to MMKV under STORAGE_KEYS.preferences.theme and restored on next launch.

Responsive Metrics

RNCopilot provides three scaling functions that adapt values to the device screen size. They use iPhone 14 (390 x 844) as the base reference:

FunctionPurposeBaseExample
rf(n)Responsive font size390px widthrf(16) scales a 16pt font proportionally
hs(n)Horizontal scale390px widthhs(20) for horizontal padding/margins
vs(n)Vertical scale844px heightvs(24) for vertical padding/margins

Using Metrics via Theme Tokens

The preferred way to use spacing is through the pre-computed theme tokens:

const styles = StyleSheet.create((theme) => ({ container: { paddingHorizontal: theme.metrics.spacing.p16, // hs(16) paddingVertical: theme.metrics.spacingV.p24, // vs(24) fontSize: theme.fonts.size.md, // rf(16) borderRadius: theme.metrics.borderRadius.md, // hs(8) }, }));

Available Spacing Tokens

theme.metrics.spacing.p4 ... p120 -- Horizontal spacing (hs-based) theme.metrics.spacingV.p4 ... p120 -- Vertical spacing (vs-based) theme.fonts.size.xxs = rf(10) theme.fonts.size.xs = rf(12) theme.fonts.size.sm = rf(14) theme.fonts.size.md = rf(16) theme.fonts.size.lg = rf(18) theme.fonts.size.xl = rf(20) theme.fonts.size.2xl = rf(24) theme.fonts.size.3xl = rf(30) theme.fonts.size.4xl = rf(36) theme.metrics.borderRadius.xs = hs(4) theme.metrics.borderRadius.sm = hs(6) theme.metrics.borderRadius.md = hs(8) theme.metrics.borderRadius.lg = hs(12) theme.metrics.borderRadius.xl = hs(16) theme.metrics.borderRadius.full = 999 theme.metrics.iconSize.xs = hs(14) theme.metrics.iconSize.sm = hs(16) theme.metrics.iconSize.md = hs(18) theme.metrics.iconSize.lg = hs(20) theme.metrics.iconSize.xl = hs(24)

Using the Raw Functions

For one-off values not covered by the token system, import the helpers directly:

import { rf, hs, vs } from '@/theme/metrics'; const styles = StyleSheet.create((theme) => ({ customBanner: { height: vs(200), paddingHorizontal: hs(32), fontSize: rf(22), }, }));

Prefer theme tokens (theme.metrics.spacing.p16) over raw function calls (hs(16)). Tokens are centralized and easier to update across the entire codebase.

Last updated on