Theming
Tokens, colour presets, dark mode, and adding your own keys.
A theme is a plain object with five groups: colors, space, radius, fontSize with
lineHeight, and fontWeight. Every recipe reads from it. Change a value and every component
that uses it updates.
Colour keys
The colour keys are fixed so that components can rely on them. Four of them describe depth.
| Key | Used for |
|---|---|
background |
Screens. |
surface |
Cards and tiles. One step above the screen. |
muted |
Controls: fields, chips, secondary buttons. One step up. |
elevated |
Floating layers: menus, popovers, sheets. |
foreground |
Default text and the active chip fill. |
mutedForeground |
Secondary text. |
primary, primaryForeground |
Primary button, checked switch, progress bar. |
secondary, secondaryForeground |
Reserved. The secondary button uses muted. |
accent, accentForeground |
Tinted badges, pressed menu rows. |
destructive, destructiveForeground |
Errors, destructive buttons and toasts. |
border |
Separators and hairlines on floating layers. |
input |
Unchecked switch track. |
ring |
Focus ring on fields and open selects. |
Keeping surface and muted one step apart is what lets a filled input sit inside a filled
card and still read as a control. If you change one, move the other.
Presets
@eoria/core ships six presets: zinc (the default), blue, green, rose, violet and
orange. Each is a light and a dark palette. The tinted ones keep zinc’s neutrals and change
primary, ring and accent, the same idea as shadcn base colours. Primary pairs were checked
against WCAG AA for the 16pt button label.
import { colorPresets, createThemes } from '@eoria/core'
configureUnistyles({ themes: createThemes(colorPresets.violet) })To switch presets while the app runs, update both registered themes in place. Adaptive light/dark keeps working and nothing remounts.
import { UnistylesRuntime } from 'react-native-unistyles'
UnistylesRuntime.updateTheme('light', (t) => ({ ...t, colors: colorPresets.rose.light }))UnistylesRuntime.updateTheme('dark', (t) => ({ ...t, colors: colorPresets.rose.dark }))Light, dark, and system
Adaptive themes are on by default. To let the user pick, turn them off first. Calling
setTheme while adaptive themes are on throws.
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.
Your own tokens
The core keys are fixed. Extra keys are yours. Augment the interface, add the values, and the theme is typed everywhere, including inside recipes.
declare module '@eoria/core' { interface EoriaTheme { brand: { coral: string; ink: string } }}
const light = { ...lightTheme, brand: { coral: '#ff6b57', ink: '#101828' } }Scales
| Group | Keys |
|---|---|
space |
0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16 (4pt grid) |
radius |
sm 8, md 12, lg 16, xl 20, 2xl 28, full |
fontSize |
xs 12, sm 14, md 16, lg 18, xl 20, 2xl 24, 3xl 30 |
lineHeight |
matched to each font size |
fontWeight |
normal, medium, semibold, bold |
Box exposes the spacing and colour scales as props, so <Box p={4} bg="surface" rounded="xl">
reads the theme without a stylesheet.