Skip to main content

Theming

The theme is the set of tokens that every component reads: color palette, typography, spacing and radius. It is part of core, so you copy it once and then edit it in your project.

Using the theme​

Wrap your app with ThemeProvider. Components read the active theme with useTheme() and the color mode with useThemeMode(), which returns the current mode and a setMode function.

ThemeProvider accepts a defaultMode prop, 'light' by default.

Palette​

Each palette color has three values you use in your UI:

  • main fills controls.
  • contrastText is the text color on top of main.
  • textColor is the same hue in a variant that meets WCAG AA as text on background.default.
import { StyleSheet, Text, View } from 'react-native';
import { useTheme } from 'tessera';

const colorKeys = [
'primary',
'secondary',
'success',
'info',
'warning',
'error',
] as const;

export function PaletteExample() {
const theme = useTheme();

return (
<View style={styles.container}>
{colorKeys.map(key => {
const color = theme.palette[key];

return (
<View
key={key}
style={[
styles.row,
{ backgroundColor: theme.palette.background.paper },
]}>
<View style={[styles.swatch, { backgroundColor: color.main }]} />
<Text style={[styles.label, { color: theme.palette.text.primary }]}>
{key}
</Text>
<Text style={[styles.sample, { color: color.textColor }]}>
Sample text
</Text>
</View>
);
})}
</View>
);
}

const styles = StyleSheet.create({
container: {
gap: 8,
},
row: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
padding: 12,
borderRadius: 8,
},
swatch: {
width: 24,
height: 24,
borderRadius: 4,
},
label: {
flex: 1,
fontSize: 14,
fontWeight: '600',
},
sample: {
fontSize: 14,
},
});
primary
Sample text
secondary
Sample text
success
Sample text
info
Sample text
warning
Sample text
error
Sample text
primary
Sample text
secondary
Sample text
success
Sample text
info
Sample text
warning
Sample text
error
Sample text

Mode toggle​

useThemeMode lets a control switch between light and dark mode.

import { Pressable, StyleSheet, Text, View } from 'react-native';
import { useTheme, useThemeMode } from 'tessera';

export function ModeToggleExample() {
const theme = useTheme();
const { mode, setMode } = useThemeMode();
const nextMode = mode === 'light' ? 'dark' : 'light';

return (
<View
style={[
styles.container,
{ backgroundColor: theme.palette.background.default },
]}>
<Text style={[styles.label, { color: theme.palette.text.primary }]}>
Current mode: {mode}
</Text>
<Pressable
accessibilityRole="button"
accessibilityLabel={`Switch to ${nextMode} mode`}
onPress={() => setMode(nextMode)}
style={({ pressed }) => [
styles.button,
{
backgroundColor: theme.palette.primary.main,
opacity: pressed ? 0.8 : 1,
},
]}>
<Text
style={[
styles.buttonLabel,
{ color: theme.palette.primary.contrastText },
]}>
Switch to {nextMode}
</Text>
</Pressable>
</View>
);
}

const styles = StyleSheet.create({
container: {
gap: 12,
padding: 16,
borderRadius: 8,
},
label: {
fontSize: 14,
},
button: {
minHeight: 48,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 16,
borderRadius: 8,
},
buttonLabel: {
fontSize: 14,
fontWeight: '500',
},
});
Current mode: light
Current mode: dark

Contrast​

Text colors are chosen to meet WCAG AA on background.default in both modes. When you add or change a color, check these controls in light and dark mode:

  • textColor on background.default: at least 4.5:1.
  • contrastText on main: at least 4.5:1.
  • contrastText on dark (the pressed state): at least 4.5:1.
  • main and dark on background.default: at least 3:1, for controls.

Disabled text is exempt, as WCAG allows.

If a color does not meet a control, adjust the tones of its ramp in tokens.ts until it does.

palette.divider is decorative. It does not reach 3:1 on the background, so do not use it as the border of a control.

Adding colors​

You can add a color ramp, for example tertiary:

  1. Add the tones from 50 to 900 to the ramp values.
  2. Add the color to palette in createAppTheme.
  3. Add its key to the AppTheme palette type.
  4. Check the contrast controls above in both modes.

Adding tokens is backward compatible. Renaming or removing a token is a breaking change for anyone who already copied core.