Themes

How to create and use themes in Tamagui.

Themes in Tamagui are powerful because they can compose off each other nicely. They are meant to be fairly small, with tokens doing much of the heavy lifting.

Think of tokens as your base variables which can be shared downwards to themes. Themes meanwhile are meant to be concise, we typically define 10-15 or so "common" theme properties that let us then style the rest of our app - things like color, colorHover, background, and backgroundHover.

Themes make assumptions about light and dark, if you define these two base themes, you'll avoid full-tree re-renders when they're changed on web.

Quick start with @tamagui/theme-base

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

Example

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

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

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 lightTheme = 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 consistent theme type to validate the rest:
type BaseTheme = typeof lightTheme
// 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 darkTranslucent: 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 lightTranslucent: 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)',
})
// use `as const` at the end here to be sure its strictly typed
export const themes = {
dark,
light,
'translucent-dark': darkTranslucent,
'translucent-light': lightTranslucent,
...colorThemes,
} as const

Subset themes

One of the real powers of Tamagui is theme nesting, which we'll explain below, but which is important to understand. 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 do:

<Theme name="dark_green">
<Button theme="subtle">Hello world</Button>
</Theme>
Component subset themes

Finally, each component in Tamagui can be set up to accept a component specific theme by passing the name property to the second argument of styled(). For example, for the Button component, we define the frame of it like so:

const ButtonFrame = styled(SizableFrame, {
name: 'Button',
tag: 'button',
})

We've decided to enforce the first letter being capitalized. This makes it easier to users to distinguish component themes, and allows Tamagui internally to avoid extra work and nesting.

The name attribute will be removed from the defaultProps and used internally by Tamagui to check for the sub-theme that matches. This means any theme named with an ending of _Button will apply. So you can add the following themes to customize buttons at any level:

  • dark_Button
  • dark_green_Button
  • dark_green_subtle_Button