Theme

Change the theme for any subtree

Change the theme for any subtree by passing its name to the Theme component.

Usage

Change the theme for any subtree:

import { Button, Theme } from 'tamagui'
export default () => (
<Theme name="dark">
<Button>I'm a dark button</Button>
<Button theme="level2">I'm using dark_level2 theme</Button>
</Theme>
)

Theme updates

Use <ThemeUpdate> to override theme values for a subtree, much like redefining a CSS custom property on one node. Listed keys override and everything else inherits from the parent theme.

import { ThemeUpdate, html } from 'tamagui'
export default () => (
<ThemeUpdate background-hover="blue-500 dark:blue-300">
<html.div bg="background hover:background-hover" />
</ThemeUpdate>
)

Values use the same flat grammar as style props. <ThemeUpdate> applies its values to a whole subtree, so it supports modifiers that describe a subtree:

  • Theme: dark:, light:, or any configured theme name. blue: applies under dark_blue, light_blue, or a theme named blue.
  • Platform: ios:, android:, web:, and native:.

Interaction states such as hover: and press:, and media queries such as sm:, describe an element rather than a subtree. They warn in development and are dropped here.

A named theme and an update compose through nesting:

<Theme name="dark">
<ThemeUpdate background="red">
{/* dark, with background overridden */}
</ThemeUpdate>
</Theme>

Keys must be theme keys or custom variables declared in the config. Unknown keys warn in development and are dropped so web and native behave the same. <ThemeUpdate> accepts children and typed theme or custom variable keys. Theme controls such as name and shallow stay on <Theme>.

Custom variables

Declare design-system values in createTamagui to make them work anywhere a theme key works: style props, useTheme(), and <ThemeUpdate> values.

const config = createTamagui({
// ...tokens, themes
variables: {
surfaceBorder: 'border-color',
disabledOpacity: 0.5,
focusRingColor: 'blue-500',
focusRingWidth: 2,
pressScale: 0.97,
accent: { light: '#005ac8', dark: '#78b4ff' },
},
})

Use and redefine them like any other theme value:

<html.div borderColor="surfaceBorder" />
<ThemeUpdate surfaceBorder="red-500">
{/* this subtree sees the red surface border */}
</ThemeUpdate>

References and units

Inline values can reference theme keys and tokens:

  • background finds the theme key or custom variable visible at the <ThemeUpdate> node.
  • size.4 and color.blue-500 reference tokens by category.
  • A bare token name such as blue-500 checks theme keys first, then scans color, space, size, radius, and zIndex tokens. Use the qualified form when a name exists in more than one category.

References resolve at the <ThemeUpdate> node and descendants inherit the result. Chains are supported, while a reference cycle drops the involved keys on both platforms with a development warning.

Numbers become px on web and stay numbers on native, matching numeric style props. Keys ending in opacity, scale, zIndex, weight, flex, grow, shrink, or ratio stay unitless. Use a string such as "10px" or px(10) for an explicit pixel value.

Nesting and updates

ThemeUpdate layers stack, with the nearest definition winning per key. A <Theme> that resolves a new name resets keys defined by that theme. Put the ThemeUpdate inside that boundary when it should survive the switch. Custom variables are defined on base themes, so sub-themes inherit their updates.

Changing update values leaves unrelated descendants alone. Tamagui tracks which keys each component reads, and only readers of a changed key update. On web, styled components restyle through CSS custom properties without a JavaScript render. JavaScript readers such as useTheme() and animation drivers see the same values on both platforms. With settings.fastSchemeChange on iOS, keys whose light and dark values are literals retain the DynamicColorIOS fast path.

Defining themes

Themes sit above tokens. Tokens are static values, while themes change per subtree like CSS variables.

Define themes however you want, but the tamagui components style themselves from a set of pre-defined keys:

  • background, background-hover, background-press, background-focus
  • border-color, border-color-hover, border-color-press, border-color-focus
  • shadow-color

These are optional, as you can always set unstyled on any Tamagui component to use your own styles.

A simplified theme definition:

import { createTamagui, createTokens } from 'tamagui'
const tokens = createTokens({
color: {
pinkDark: '#610c62',
pinkLight: '#f17efc',
},
// ... see configuration docs for required tokens
})
export default createTamagui({
tokens,
themes: {
dark: {
background: '#000',
color: '#fff',
},
light: {
color: '#000',
background: '#fff',
},
dark_pink: {
background: tokens.color.pinkDark,
color: tokens.color.pinkLight,
},
light_pink: {
background: tokens.color.pinkLight,
color: tokens.color.pinkDark,
},
},
})

Passing tokens to themes will reduce CSS, but is not required.

Access theme values through styled() or directly on a component:

const P = styled(Text, {
color: 'color-11'
})
// or directly
<Text color="color-11" />

Because we defined sub-themes light_pink and dark_pink, the Theme component will now let us do this:

import { Button, Theme } from 'tamagui'
export default () => {
return (
<Theme name="dark">
<Button>I have the theme dark</Button>
<Theme name="pink">
<Button>I have the theme dark_pink</Button>
</Theme>
</Theme>
)
}

Notice we just use the name pink.

For larger typed theme suites, see Creating themes, which introduces createThemes, recipe trees, scales, and relative levels.