Themes

How to create and use themes in Tamagui.

Themes map neatly to CSS variables for your components: they should contain colors and other values that you'd want to contextually change at any point in your React tree.

All themes must be of the same type, an object with consistent values. If you just want to dive in and copy a complete theme, check out the Quick Start.

You make a theme with createTheme, like so:

const dark = createTheme({
background: '#000',
color: '#fff',
// define any key to any string or number value
})

Using tokens

Tokens are CSS variables that don't change contextually. Think of them as your constants. You should put all your color values into tokens first, like so:

const tokens = createTokens({
color: {
black: '#000',
white: '#fff',
},
})

Then, you can use them in any theme:

const dark = createTheme({
background: tokens.color.black,
color: tokens.color.white,
})

Think of tokens as your base constants that share downwards.

Tamagui make some assumptions about the themes dark and light. If you use them, changing between the two will avoid re-rendering the whole tree.

Subset themes

One of the real powers of Tamagui is theme nesting (we'll explain more on the importance and usage below). If you define a theme with the name in the form [parentName]_[subName], Tamagui then accepts <Theme name="[subName]" /> as though it's valid.

You can do this as many times as you'd like. For example you can have the following themes:

  • dark_green_subtle
  • light_green_subtle

And you're able to then access them (fully typed):

<Theme name="dark">
<Theme name="green">
<Button theme="subtle">Hello world</Button>
</Theme>
</Theme>

You can also access a specific subset more directly:

<Theme name="dark">
<Button theme="green_subtle">Hello world</Button>
</Theme>

Component themes

Every Tamagui styled() component looks for it's own specific theme if you pass it the name property. For example:

import { styled, Stack } from '@tamagui/core'
const Circle = styled(Stack, {
name: 'Circle',
backgroundColor: '$background',
})

The name attribute will be removed from the defaultProps and used internally by Tamagui to check for a sub-theme that ends with _Circle.

Now you can create the default theme for all Circle components at any level of nesting:

const dark_Circle = createTheme({
background: 'darkred',
color: 'white',
})
const light_Circle = createTheme({
background: 'lightred',
color: 'black',
})

Component themes must have the first letter capitalized.

  • dark_Circle
  • dark_green_Circle
  • dark_green_subtle_Circle

This is an incredibly powerful and unique feature that allows authors of UI components control over design, while still letting users customize them completely.

wwwwwwwwwwwwwwwwwww

While @tamagui/core isn't prescriptive at all, tamagui is. This is because standardizing on specific shared theme keys unlocks huge upside. We recommend authors to use these values as well, to enable maximum sharing.

In tamagui, all components will look for the following keys:

  • background
  • color
  • borderColor
  • shadowColor

...plus all the pseudo variants for each, eg, backgroundHover, backgroundPress, and backgroundFocus.

You can of course do all of this yourself in your own design system with styled:

If you are building a component with more than one sub-components, you can follow this pattern:

import { Text, Stack, styled, GetProps } from '@tamagui/core'
const ButtonFrame = styled(Stack, {
name: 'Button'
backgroundColor: '$background'
})
const ButtonText = styled(Text, {
name: 'ButtonText'
color: '$color'
})
type ButtonProps = GetProps<typeof ButtonFrame>
// note: extractable will tell the tamagui compiler to optimize usages of this:
export const Button = ButtonFrame.extractable(({ children, ...props }: ButtonProps) => {
return (
<ButtonFrame {...props}>
<ButtonText>
{children}
</ButtonText>
</ButtonFrame>
)
})

And now you can add two themes: dark_Button and dark_ButtonText, and override their default styles.

Quick start

To get started quickly, you can use the themes we've developed alongside this site and with other apps, @tamagui/theme-base. It's even easier to see how it all comes together by using create-tamagui-app to bootstrap.

To install, just add import it and add it to your tamagui.config.ts:

import { createInterFont } from '@tamagui/font-inter'
import { color, radius, size, space, themes, zIndex } from '@tamagui/theme-base'
import { createTamagui, createTokens } from 'tamagui'
const inter = createInterFont()
const tokens = createTokens({
font: {
heading: inter,
body: inter,
},
size,
space,
zIndex,
color,
radius,
})
const config = createTamagui({
themes,
tokens,
})
export type Conf = typeof config
declare module 'tamagui' {
interface TamaguiCustomConfig extends Conf {}
}
export default config

Full Example

Let's start with an example of inline styling with a subset of the configuration:

import { TamaguiProvider, createTokens, createTamagui, YStack, Theme } from 'tamagui'
const tokens = createTokens({
color: {
darkRed: '#550000'
lightRed: '#ff0000'
}
// ... see configuration docs for required tokens
})
const config = createTamagui({
tokens,
themes: {
dark: {
red: tokens.color.darkRed,
},
light: {
red: tokens.color.lightRed,
}
}
})
export const App = () => (
<TamaguiProvider config={config} defaultTheme="light">
<YStack backgroundColor="$red" />
<Theme name="dark">
<YStack backgroundColor="$red" />
</Theme>
</TamaguiProvider>
)

In this example we've set up darkRed and lightRed variables and a a dark and light theme that use those variables. Tamagui will handle defining:

:root {
--colors-dark-red: #550000;
--colors-light-red: #ff0000;
}
.tui_dark {
--red: var(--colors-dark-red);
}
.tui_light {
--red: var(--colors-light-red);
}

Which will automatically apply at runtime, or can be gathered for use in SSR using Tamagui.getCSS().

Finally, the compiler on web will extract your views roughly as so:

export const App = () => (
<Provider defaultTheme="light">
<div className="baCo-2nesi3" />
<Theme name="dark">
<div className="baCo-2nesi3" />
</Theme>
</Provider>
)
// CSS output:
// .color-2nesi3 { background-color: var(--red); }

Ensuring valid types

Here's what we've landed on which helps ensure everything is typed properly. Use createTheme, which is a simple helper for creating a theme and having all the values turned into variables. Keep themes in a separate themes.ts file, and structure it like this:

import { createTheme } from 'tamagui'
import { tokens } from './tokens'
const light = createTheme({
background: '#fff',
backgroundHover: tokens.color.gray3,
backgroundPress: tokens.color.gray4,
backgroundFocus: tokens.color.gray5,
borderColor: tokens.color.gray4,
borderColorHover: tokens.color.gray6,
color: tokens.color.gray12,
colorHover: tokens.color.gray11,
colorPress: tokens.color.gray10,
colorFocus: tokens.color.gray6,
shadowColor: tokens.color.grayA5,
shadowColorHover: tokens.color.grayA6,
})
// note: we set up a single consistent base type to validate the rest:
type BaseTheme = typeof light
// the rest of the themes use BaseTheme
const dark: BaseTheme = {
background: '#000',
backgroundHover: tokens.color.gray2Dark,
backgroundPress: tokens.color.gray3Dark,
backgroundFocus: tokens.color.gray4Dark,
borderColor: tokens.color.gray3Dark,
borderColorHover: tokens.color.gray4Dark,
color: '#ddd',
colorHover: tokens.color.gray11Dark,
colorPress: tokens.color.gray10Dark,
colorFocus: tokens.color.gray6Dark,
shadowColor: tokens.color.grayA6,
shadowColorHover: tokens.color.grayA7,
}
// if you need to add non-token values, use createTheme
const dark_translucent: BaseTheme = createTheme({
...dark,
background: 'rgba(0,0,0,0.7)',
backgroundHover: 'rgba(0,0,0,0.5)',
backgroundPress: 'rgba(0,0,0,0.25)',
backgroundFocus: 'rgba(0,0,0,0.1)',
})
const light_translucent: BaseTheme = createTheme({
...light,
background: 'rgba(255,255,255,0.85)',
backgroundHover: 'rgba(250,250,250,0.85)',
backgroundPress: 'rgba(240,240,240,0.85)',
backgroundFocus: 'rgba(240,240,240,0.7)',
})
// note the steps here
// we recommend doing this because it avoids a category of confusing type errors
// 1. to get ThemeNames/Theme, first create an object with all themes
const allThemes = {
dark,
light,
dark_translucent,
light_translucent,
...colorThemes,
}
// 2. then get the name type
type ThemeName = keyof typeof allThemes
// 3. then, create a Themes type that explicitly maps ThemeName => BaseTheme
type Themes = {
[key in ThemeName]: BaseTheme
}
// 4. finally, export it with the stricter type
export const themes: Themes = allThemes

Dynamic Themes

Sometimes you want to defer loading themes, or change existing theme values at runtime. Tamagui exports two helpers for this, respectively: addTheme and updateTheme.

addTheme

Theme: none

updateTheme