Skip to content

Colors

Stratum follows the Color Scarcity Principle (ADR-010): one accent color + black + white + grays. Color is powerful because it’s scarce. The best-in-class apps (Wealthsimple, Coinbase, Airbnb) use this pattern — a single accent hue creates strong visual hierarchy without competing colors.

There are two layers:

  1. Palette (colors.ts) — raw values: palette.gray900, palette.brand500. This is an internal implementation detail. Never import from the palette in components.

  2. Semantic tokens (via theme.colors.*) — named by meaning, not value. theme.colors.textPrimary is black in light mode, near-white in dark mode. When switching themes or modes, the semantic names stay the same but the values update.

// ✓ Semantic — updates with theme and dark mode
const theme = useTheme();
<Text style={{ color: theme.colors.textPrimary }}>Hello</Text>
// ✗ Palette — doesn't respond to theme/dark mode changes
import { palette } from '@/tokens/colors'; // Never do this in components
TokenDescription
theme.colors.backgroundPage background
theme.colors.surfaceCard and panel background
theme.colors.surfaceRaisedElevated surface (above surface level)
TokenDescription
theme.colors.textPrimaryMain body text
theme.colors.textSecondarySupporting text, slightly dimmed
theme.colors.textMutedPlaceholder, timestamps, fine print
theme.colors.textInverseText on dark backgrounds
theme.colors.textOnAccentText inside accent-colored containers
TokenDescription
theme.colors.accentPrimary brand color
theme.colors.accentHoverDarker accent for press states
theme.colors.accentSubtleVery light accent tint (chips, badges, pills)

These colors communicate functional meaning. Never use them decoratively.

TokenDescription
theme.colors.successSuccess, complete, verified
theme.colors.successSubtleLight success tint (badge background)
theme.colors.warningPending, review, expiring
theme.colors.warningSubtleLight warning tint
theme.colors.errorError, failed, destructive
theme.colors.errorSubtleLight error tint
TokenDescription
theme.colors.borderDefault divider and input border
theme.colors.borderStrongEmphasized border
theme.colors.borderSubtleVery light separator
TokenDescription
theme.colors.overlayModal scrim background
ThemeLight accentDark accent
Slate#000000 (black)#FFFFFF (white)
ObsidianBrand teal/indigoBrighter variant
Quartz#2563EB (electric blue)Adjusted blue
const theme = useTheme();
// In a StyleSheet:
const styles = StyleSheet.create({
container: { backgroundColor: theme.colors.background },
card: { backgroundColor: theme.colors.surface, borderColor: theme.colors.border },
title: { color: theme.colors.textPrimary },
caption: { color: theme.colors.textMuted },
});

Do: Use semantic color names — they update automatically across themes and modes.

Don’t: Import from src/tokens/colors.ts (palette) in components.

// ✗
import { palette } from '@/tokens/colors';
style={{ color: palette.gray900 }}
// ✓
const theme = useTheme();
style={{ color: theme.colors.textPrimary }}

Don’t: Add a second accent color to a component or theme. If your design calls for a second accent, extend the AppTheme interface — but the default system intentionally uses one.

When creating a custom theme with createTheme(), you override semantic slots — not palette values:

import { createTheme } from '@/tokens/themes/createTheme';
import { slateThemeLight } from '@/tokens/themes/slate';
const brandTheme = createTheme(slateThemeLight, {
colors: {
accent: '#E11D48',
accentHover: '#BE123C',
accentSubtle: '#FFF1F2',
},
});