# Theming

Pick a preset, change what you need, and handle dark mode.

[Documentation](https://eoria.adamtrip.pt/start/theming/)

A theme is a plain object of colours, sizes and fonts. Every component reads its styles from
that object, so one change reaches every component that uses the value. You do not write the
object from nothing. You start from a preset.

## Pick a preset

A preset is a complete look: a light palette, a dark palette, the corner radii, the control
heights and a typeface. `@eoria/core` ships six. Pick one, then copy the line that uses it or
the whole object.

### zinc

Neutral, native.

```ts
import { configureUnistyles, createThemes, presets } from '@eoria/core'

configureUnistyles({ themes: createThemes(presets.zinc) })
```

```ts
import { configureUnistyles, createThemes, type Preset } from '@eoria/core'

// Copied from the zinc preset. Change any value.
const brand: Preset = {
  light: {
    background: '#ffffff',
    foreground: '#0a0a0a',
    surface: '#f5f5f5',
    elevated: '#ffffff',
    primary: '#171717',
    primaryForeground: '#fafafa',
    secondary: '#e9e9e9',
    secondaryForeground: '#171717',
    muted: '#e9e9e9',
    mutedForeground: '#6b6b6b',
    accent: '#e4e4e7',
    accentForeground: '#171717',
    destructive: '#dc2626',
    destructiveForeground: '#fafafa',
    destructiveText: '#c51f1f',
    border: '#e5e5e5',
    input: '#e5e5e5',
    ring: '#a1a1a1',
  },
  dark: {
    background: '#0a0a0a',
    foreground: '#fafafa',
    surface: '#171717',
    elevated: '#222222',
    primary: '#fafafa',
    primaryForeground: '#171717',
    secondary: '#2a2a2a',
    secondaryForeground: '#fafafa',
    muted: '#2a2a2a',
    mutedForeground: '#a1a1a1',
    accent: '#333333',
    accentForeground: '#fafafa',
    destructive: '#7f1d1d',
    destructiveForeground: '#fafafa',
    destructiveText: '#f87171',
    border: '#262626',
    input: '#262626',
    ring: '#525252',
  },
  radius: { sm: 8, md: 12, lg: 16, xl: 20, '2xl': 28, full: 9999, control: 12, card: 20 },
  control: { sm: 40, md: 52, lg: 56 },
  stroke: 0,
  shadow: { opacity: 0.12, radius: 16, offset: 6 },
  fontWeight: { normal: '400', medium: '500', semibold: '600', bold: '700' },
  font: {  },
}

configureUnistyles({ themes: createThemes(brand) })
```

### moss

Warm, rounder.

```ts
import { configureUnistyles, createThemes, presets } from '@eoria/core'

configureUnistyles({ themes: createThemes(presets.moss) })
```

```ts
import { configureUnistyles, createThemes, type Preset } from '@eoria/core'

// Copied from the moss preset. Change any value.
const brand: Preset = {
  light: {
    background: '#faf8f3',
    foreground: '#1c1a15',
    surface: '#f0ece2',
    elevated: '#ffffff',
    primary: '#2f6b3f',
    primaryForeground: '#f4fbf5',
    secondary: '#e4dfd2',
    secondaryForeground: '#1c1a15',
    muted: '#e4dfd2',
    mutedForeground: '#716b5e',
    accent: '#dcebd9',
    accentForeground: '#1f4a2b',
    destructive: '#b93a2b',
    destructiveForeground: '#fff5f3',
    destructiveText: '#ad3526',
    border: '#e0dacd',
    input: '#e0dacd',
    ring: '#2f6b3f',
  },
  dark: {
    background: '#14130f',
    foreground: '#f2eee4',
    surface: '#1f1d17',
    elevated: '#2b2821',
    primary: '#7fc48f',
    primaryForeground: '#0f2214',
    secondary: '#2f2c24',
    secondaryForeground: '#f2eee4',
    muted: '#2f2c24',
    mutedForeground: '#a59f90',
    accent: '#24362a',
    accentForeground: '#d7f0dc',
    destructive: '#8a2c22',
    destructiveForeground: '#fff5f3',
    destructiveText: '#f08c7c',
    border: '#2c2922',
    input: '#2c2922',
    ring: '#7fc48f',
  },
  radius: { sm: 10, md: 16, lg: 22, xl: 28, '2xl': 36, full: 9999, control: 9999, card: 28 },
  control: { sm: 44, md: 56, lg: 60 },
  stroke: 0,
  shadow: { opacity: 0.16, radius: 28, offset: 12 },
  fontWeight: { normal: '400', medium: '500', semibold: '600', bold: '600' },
  font: {
    heading: { ios: 'Georgia', android: 'serif', web: 'Georgia, "Times New Roman", serif' },
  },
}

configureUnistyles({ themes: createThemes(brand) })
```

### cobalt

Cool, sharper.

```ts
import { configureUnistyles, createThemes, presets } from '@eoria/core'

configureUnistyles({ themes: createThemes(presets.cobalt) })
```

```ts
import { configureUnistyles, createThemes, type Preset } from '@eoria/core'

// Copied from the cobalt preset. Change any value.
const brand: Preset = {
  light: {
    background: '#ffffff',
    foreground: '#0b1220',
    surface: '#f8fafc',
    elevated: '#ffffff',
    primary: '#1d4ed8',
    primaryForeground: '#f5f8ff',
    secondary: '#e6ecf3',
    secondaryForeground: '#0b1220',
    muted: '#eef2f7',
    mutedForeground: '#5b6678',
    accent: '#dbe6ff',
    accentForeground: '#1e3a8a',
    destructive: '#dc2626',
    destructiveForeground: '#fff5f5',
    destructiveText: '#c51f1f',
    border: '#cfd8e3',
    input: '#b7c3d3',
    ring: '#1d4ed8',
  },
  dark: {
    background: '#0b1220',
    foreground: '#e6edf7',
    surface: '#0f172a',
    elevated: '#1b2740',
    primary: '#60a5fa',
    primaryForeground: '#06101f',
    secondary: '#223150',
    secondaryForeground: '#e6edf7',
    muted: '#223150',
    mutedForeground: '#94a3b8',
    accent: '#1e2f52',
    accentForeground: '#dbe6ff',
    destructive: '#991b1b',
    destructiveForeground: '#fff5f5',
    destructiveText: '#f87171',
    border: '#243554',
    input: '#34496f',
    ring: '#60a5fa',
  },
  radius: { sm: 4, md: 6, lg: 8, xl: 10, '2xl': 12, full: 9999, control: 6, card: 8 },
  control: { sm: 34, md: 42, lg: 48 },
  stroke: 1,
  shadow: { opacity: 0.06, radius: 8, offset: 2 },
  fontWeight: { normal: '400', medium: '600', semibold: '700', bold: '700' },
  font: {
    body: {
      ios: 'IBM Plex Sans',
      android: 'IBM Plex Sans',
      web: "'IBM Plex Sans', 'IBM Plex Sans Variable', system-ui, sans-serif",
    },
  },
}

configureUnistyles({ themes: createThemes(brand) })
```

### iris

Product, hairline cards.

```ts
import { configureUnistyles, createThemes, presets } from '@eoria/core'

configureUnistyles({ themes: createThemes(presets.iris) })
```

```ts
import { configureUnistyles, createThemes, type Preset } from '@eoria/core'

// Copied from the iris preset. Change any value.
const brand: Preset = {
  light: {
    background: '#f6f7f9',
    foreground: '#12131a',
    surface: '#ffffff',
    elevated: '#ffffff',
    primary: '#4a44d4',
    primaryForeground: '#ffffff',
    secondary: '#e5e8ef',
    secondaryForeground: '#12131a',
    muted: '#e9ebf1',
    mutedForeground: '#5f6577',
    accent: '#e7e6fb',
    accentForeground: '#352fa8',
    destructive: '#d92d20',
    destructiveForeground: '#ffffff',
    destructiveText: '#c4281c',
    border: '#e2e5eb',
    input: '#d3d8e2',
    ring: '#4a44d4',
  },
  dark: {
    background: '#0c0d11',
    foreground: '#eceef4',
    surface: '#15171d',
    elevated: '#1e2129',
    primary: '#6a64ea',
    primaryForeground: '#ffffff',
    secondary: '#242730',
    secondaryForeground: '#eceef4',
    muted: '#242730',
    mutedForeground: '#9aa1b2',
    accent: '#25244d',
    accentForeground: '#d6d5fd',
    destructive: '#b42318',
    destructiveForeground: '#ffffff',
    destructiveText: '#f97066',
    border: '#262933',
    input: '#343846',
    ring: '#8985f2',
  },
  radius: { sm: 6, md: 10, lg: 14, xl: 18, '2xl': 24, full: 9999, control: 10, card: 16 },
  control: { sm: 36, md: 46, lg: 52 },
  stroke: 1,
  shadow: { opacity: 0.08, radius: 14, offset: 4 },
  fontWeight: { normal: '400', medium: '500', semibold: '600', bold: '700' },
  font: {
    body: {
      ios: 'Geist',
      android: 'Geist',
      web: "'Geist', 'Geist Variable', system-ui, sans-serif",
    },
  },
}

configureUnistyles({ themes: createThemes(brand) })
```

### tide

Tonal, soft corners.

```ts
import { configureUnistyles, createThemes, presets } from '@eoria/core'

configureUnistyles({ themes: createThemes(presets.tide) })
```

```ts
import { configureUnistyles, createThemes, type Preset } from '@eoria/core'

// Copied from the tide preset. Change any value.
const brand: Preset = {
  light: {
    background: '#f5f9f9',
    foreground: '#0c1c1e',
    surface: '#e7f0f0',
    elevated: '#ffffff',
    primary: '#0a6468',
    primaryForeground: '#f0fafa',
    secondary: '#d7e5e5',
    secondaryForeground: '#0c1c1e',
    muted: '#d7e5e5',
    mutedForeground: '#4b5f62',
    accent: '#c6e4e2',
    accentForeground: '#07474a',
    destructive: '#c8372d',
    destructiveForeground: '#fff6f5',
    destructiveText: '#b53027',
    border: '#cfdddd',
    input: '#c3d4d4',
    ring: '#0a6468',
  },
  dark: {
    background: '#071314',
    foreground: '#e6f1f1',
    surface: '#0f2022',
    elevated: '#1a2f32',
    primary: '#6ad1c8',
    primaryForeground: '#04211f',
    secondary: '#1b3336',
    secondaryForeground: '#e6f1f1',
    muted: '#1b3336',
    mutedForeground: '#94aaad',
    accent: '#123f40',
    accentForeground: '#c8f0ec',
    destructive: '#9b2c24',
    destructiveForeground: '#fff6f5',
    destructiveText: '#f4877d',
    border: '#1a2e31',
    input: '#2a484c',
    ring: '#6ad1c8',
  },
  radius: { sm: 8, md: 12, lg: 16, xl: 22, '2xl': 30, full: 9999, control: 16, card: 26 },
  control: { sm: 40, md: 50, lg: 58 },
  stroke: 0,
  shadow: { opacity: 0.14, radius: 22, offset: 10 },
  fontWeight: { normal: '500', medium: '600', semibold: '700', bold: '700' },
  font: {
    body: {
      ios: 'Manrope',
      android: 'Manrope',
      web: "'Manrope', 'Manrope Variable', system-ui, sans-serif",
    },
  },
}

configureUnistyles({ themes: createThemes(brand) })
```

### plum

Editorial, serif titles.

```ts
import { configureUnistyles, createThemes, presets } from '@eoria/core'

configureUnistyles({ themes: createThemes(presets.plum) })
```

```ts
import { configureUnistyles, createThemes, type Preset } from '@eoria/core'

// Copied from the plum preset. Change any value.
const brand: Preset = {
  light: {
    background: '#ffffff',
    foreground: '#1a1517',
    surface: '#f5f3f1',
    elevated: '#ffffff',
    primary: '#5a2348',
    primaryForeground: '#fbf3f8',
    secondary: '#eae7e4',
    secondaryForeground: '#1a1517',
    muted: '#eae7e4',
    mutedForeground: '#6c6366',
    accent: '#f1e4ec',
    accentForeground: '#5a2348',
    destructive: '#c4321f',
    destructiveForeground: '#fff6f3',
    destructiveText: '#b82e1c',
    border: '#e2dedb',
    input: '#e2dedb',
    ring: '#5a2348',
  },
  dark: {
    background: '#120e10',
    foreground: '#f3eef0',
    surface: '#1c1619',
    elevated: '#292024',
    primary: '#e0b3d3',
    primaryForeground: '#2b0f22',
    secondary: '#2d2529',
    secondaryForeground: '#f3eef0',
    muted: '#2d2529',
    mutedForeground: '#a99da3',
    accent: '#3a2033',
    accentForeground: '#f1d9e9',
    destructive: '#a12c1c',
    destructiveForeground: '#fff6f3',
    destructiveText: '#f2846f',
    border: '#2b2327',
    input: '#2b2327',
    ring: '#e0b3d3',
  },
  radius: { sm: 2, md: 4, lg: 6, xl: 10, '2xl': 14, full: 9999, control: 4, card: 10 },
  control: { sm: 40, md: 52, lg: 58 },
  stroke: 0,
  shadow: { opacity: 0.1, radius: 24, offset: 10 },
  fontWeight: { normal: '400', medium: '500', semibold: '500', bold: '600' },
  font: {
    body: {
      ios: 'Instrument Sans',
      android: 'Instrument Sans',
      web: "'Instrument Sans', 'Instrument Sans Variable', system-ui, sans-serif",
    },
    heading: {
      ios: 'Newsreader',
      android: 'Newsreader',
      web: "'Newsreader', 'Newsreader Variable', Georgia, serif",
    },
  },
}

configureUnistyles({ themes: createThemes(brand) })
```

`createThemes` turns a preset into the `light` and `dark` themes Unistyles needs, and adds the
spacing and type scales every preset shares. The code goes in the `src/unistyles.ts` file that
`init` wrote.

`zinc` and `moss` use the system font. `cobalt`, `iris`, `tide` and `plum` name a typeface that
your app has to embed, and fall back to the system font until it does. [Fonts](https://eoria.adamtrip.pt/start/fonts.md)
shows how.

## Change a preset

You rarely need the whole object. Spread a preset and replace the parts you want.

Your brand colour on the main button, in both modes:

```ts
import { configureUnistyles, createThemes, presets } from '@eoria/core'

const brand = {
  ...presets.zinc,
  light: { ...presets.zinc.light, primary: '#0f62fe', primaryForeground: '#ffffff' },
  dark: { ...presets.zinc.dark, primary: '#78a9ff', primaryForeground: '#0a0a0a' },
}

configureUnistyles({ themes: createThemes(brand) })
```

Pill buttons and fields, shorter controls, outlined cards:

```ts
const brand = {
  ...presets.zinc,
  radius: { ...presets.zinc.radius, control: 9999 },
  control: { sm: 36, md: 44, lg: 52 },
  stroke: 1,
}
```

Check the contrast of a new `primary` against `primaryForeground`. The shipped pairs pass WCAG AA
for the 16pt button label. [Tokens](https://eoria.adamtrip.pt/start/tokens.md) lists every key and what it drives.

## Light, dark, and system

The app follows the OS appearance by default. To let the user pick, turn adaptive themes off
first. Calling `setTheme` while they are on throws.

```ts
import { UnistylesRuntime } from 'react-native-unistyles'

export function setMode(mode: 'system' | 'light' | 'dark') {
  if (mode === 'system') {
    UnistylesRuntime.setAdaptiveThemes(true)
    return
  }
  if (UnistylesRuntime.hasAdaptiveThemes) UnistylesRuntime.setAdaptiveThemes(false)
  UnistylesRuntime.setTheme(mode)
}
```

`useUnistyles().rt.themeName` and `rt.hasAdaptiveThemes` re-render when either changes, so a
segmented control bound to them stays in sync.

## Switch preset while the app runs

Update both registered themes in place. The light and dark switch keeps working and nothing
remounts.

```ts
import { UnistylesRuntime } from 'react-native-unistyles'

const themes = createThemes(presets.cobalt)
UnistylesRuntime.updateTheme('light', (t) => ({ ...t, ...themes.light }))
UnistylesRuntime.updateTheme('dark', (t) => ({ ...t, ...themes.dark }))
```

## Next

- [Tokens](https://eoria.adamtrip.pt/start/tokens.md) is the reference: every colour key, the shape groups, the scales
  and how to add keys of your own.
- [Fonts](https://eoria.adamtrip.pt/start/fonts.md) covers embedding a preset's typeface or your own.
- [Recipes](https://eoria.adamtrip.pt/start/recipes.md) shows how a component reads the theme.
