eoria

Theming

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

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.

Neutral, native. System font.

light
dark

Use it as it is

src/unistyles.ts
import { configureUnistyles, createThemes, presets } from '@eoria/core'
configureUnistyles({ themes: createThemes(presets.zinc) })

Or copy it and change it

src/unistyles.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) })

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 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:

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:

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 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.

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.

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 is the reference: every colour key, the shape groups, the scales and how to add keys of your own.
  • Fonts covers embedding a preset’s typeface or your own.
  • Recipes shows how a component reads the theme.