Configuration
Custom themes, tokens, shorthands, and media queries.
Create a tamagui.config.ts
in the root of your app to set up your design system. It should only be imported by the top-level file that runs React.render
, to ensure fast refresh works.
Quick Start
You can use @tamagui/config-base
a totally complete config out of the box, the same one this site uses.
import { config } from '@tamagui/config-base'import { createTamagui, setupReactNative } from '@tamagui/core'import * as ReactNative from 'react-native'// if using only @tamagui/core with `react-native` components// if using `tamagui` this isn't necessarysetupReactNative(ReactNative)const appConfig = createTamagui(config)export type AppConfig = typeof appConfigdeclare module '@tamagui/core' {// overrides TamaguiCustomConfig so your custom types// work everywhere you import `tamagui`interface TamaguiCustomConfig extends AppConfig {}}export default appConfig
The source and type output of that config is also useful as a reference for building your own custom config.
Another quick start that gives a bit more customization is using @tamagui/shorthands
for our preset shorthands, and @tamagui/theme-base
for the same themes and tokens used for this site. Then you'll just want to bring along any fonts, media queries, and animations you need.
import { createTamagui } from '@tamagui/core'import { shorthands } from '@tamagui/shorthands'import { themes, tokens } from '@tamagui/theme-base'const appConfig = createTamagui({themes,tokens,shorthands,})export type AppConfig = typeof appConfigdeclare module '@tamagui/core' {interface TamaguiCustomConfig extends AppConfig {}}export default appConfig
Depending on if you're using just @tamagui/core
, tamagui
, or your own custom design library, be sure the import of createTamagui
and declare module
type matches to the one you're using.
Overview
Let's start with an example of a complete tamagui.config.ts
:
import { createFont, createTamagui, createTokens } from '@tamagui/core'import { createMedia } from '@tamagui/react-native-media-driver'const interFont = createFont({family: 'Inter, Helvetica, Arial, sans-serif',size: {1: 12,2: 14,3: 15,// ...},lineHeight: {1: 17,2: 22,3: 25,// ...},weight: {4: '300',6: '600',},letterSpacing: {4: 0,8: -1,},// (native only) swap out fonts by face/styleface: {200: { normal: 'InterLight', italic: 'InterItalic' },400: { normal: 'Inter' },800: { normal: 'InterBold' },},// you may also set `transform` as textTransform values// and `style` as fontStyle values})const size = {0: 0,1: 5,2: 10,// ....}export const tokens = createTokens({size,space: { ...size, '-1': -5, '-2': -10 },radius: { 0: 0, 1: 3 },zIndex: { 0: 0, 1: 100, 2: 200 },color: {white: '#fff',black: '#000',},})const config = createTamagui({fonts: {// for tamagui, heading and body are assumedheading: interFont,body: interFont,},tokens,themes: {light: {bg: '#f2f2f2',color: tokens.color.black,},dark: {bg: '#111',color: tokens.color.white,},},// `@tamagui/core` doesn't provide media query capabilities out of the box// for native as it is de-coupled from react-native.// For web-only, media queries work out of the box and you can avoid the// `createMedia` call here by passing the media object directly.// If targeting React Native, add this driver and `createMedia` call.// `tamagui` and `config-base` do this for you automatically.media: createMedia({sm: { maxWidth: 860 },gtSm: { minWidth: 860 + 1 },short: { maxHeight: 820 },hoverNone: { hover: 'none' },pointerCoarse: { pointer: 'coarse' },}),// optional:// add custom shorthand propsshorthands: {px: 'paddingHorizontal',f: 'flex',w: 'width',},// Experimental / advanced, only for overriding the core component styles// Prefer to use styled() for building your own, only useful for edge cases.defaultProps: {Text: {// override any default props here},},})type AppConfig = typeof config// this will give you types for your components// note - if using your own design system, put the package name here instead of tamaguideclare module 'tamagui' {interface TamaguiCustomConfig extends AppConfig {}}export default config
The createTamagui
function receives a configuration object:
tokens
: UsecreateTokens
to generate variables in your theme and app.theme
: Define your design theme, which map to CSS properties.media
: Define reusable responsive media queries.shorthands
: Define any props you want to expand to style values, keys being the shorthand and values being the expanded style prop.defaultProps
: For more advanced uses, you can override all namedstyled()
components initial values. These merge downwards, sostyled(Text, { name: 'Paragraph' })
will get any defaultProps set forText
.
Note, for tamagui
, it expects certain things on tokens. Space and size should both have 1-10 defined, as well as a value for true
that maps to 4
.
The compiler will parse your tamagui.config.ts file at build-time. For this reason, we
comment keeping it relatively simple. Don't import heavy dependencies, and prefer to
import createTamagui
and other helpers directly from @tamagui/core.
Add Provider
Import and use the Tamagui Provider
component at the top component in your app.
import { TamaguiProvider } from 'tamagui'import config from './tamagui.config'export default function App() {return (<TamaguiProvider config={config}><AppContents /></TamaguiProvider>)}
Using Tamagui Provider
TamaguiProvider takes a few optional properties:
Props
defaultTheme (required)
string
The initial top level theme.
disableRootThemeClass
boolean
Disable inserting a theme class in the DOM or context, allowing you to manually place it higher. For custom use cases like integration with next-theme.
disableInjectCSS
boolean
By default Tamagui inserts CSS with a useLayoutEffect on load. But if you're not setting up SSR you'll want to use getCSS() instead and enable this prop.
disableRootThemeClass
boolean
Tamagui will insert your current root theme onto the body unless this property is enabled. This typically can be left off.
If you use disableInjectCSS
, you'll need to use this somewhere to insert your CSS into head:
import Tamagui from './tamagui.config'const css = Tamagui.getCSS()// insert into your head
If using Next.js, see how to do this in the Next.js guide.
Make sure the style tag you insert with the getCSS
styles is inserted after any
react-native-web
style tags to ensure Tamagui can override the built-in styles.
Tokens
Tokens have a structure inspired by but divergent from the Theme UI spec . They are mapped to CSS variables at build time.
Font tokens
The font tokens are a bit special and are created with createFont
:
const interFont = createFont({family: 'Inter, Helvetica, Arial, sans-serif',size: {1: 12,2: 14,3: 15,// ...},lineHeight: {1: 17,2: 22,3: 25,// ...},weight: {4: '300',6: '600',},letterSpacing: {4: 0,8: -1,},// for native only, alternate family based on weight/styleface: {// pass in weights as keys700: { normal: 'InterBold', italic: 'InterBold-Italic' },800: { normal: 'InterBold', italic: 'InterBold-Italic' },900: { normal: 'InterBold', italic: 'InterBold-Italic' },},})
Note, you don't need to use numbered keys, you can use sm
or tiny
or whatever you'd
like. But you do want keep those keys consistent.
This gives you a lot of power over customizing every aspect of your design based on each font family. In other styling libraries that follow the Theme UI spec, you generally don't group your size/lineHeight/weight/etc tokens by the family, which means you are forced to choose a single vertical rhythm no matter the font.
Things to note:
- The keys of
size
,lineHeight
,weight
, andletterSpacing
are meant to match. - Define the full set of keys on
size
, the rest can be a subset. - Missing keys from partially defined objects will be filled in.
- In the example above,
weight
is only defined at4
and6
. - At creation Tamagui fills in the missing keys with previous value, or the next one if no previous exists. So weight
1
===300
, weight5
===300
, and weight7
===600
.
- In the example above,
Non-font tokens
The rest of the tokens categories besides font are flatter. The space
and size
generally share keys, and that space can generally use negative keys as well.
// passed into createTamaguiconst tokens = createTokens({color: {white: '#fff',black: '#000',},})
You access tokens then by using $
prefixes in your values. Tamagui knows which tokens to use based on the style property you use.
const App = () => (<Text fontSize="$lg" lineHeight="$lg" fontFamily="$mono" color="$white">Hello world</Text>)
One final note: using tokens with themes. Tokens are considered a "fallback" to themes, so any values you define in your theme will override the token. The next section will explain this further.
Strict tokens
If you'd like to enforce only allowing token values from your theme, you can override the ThemeValueFallback
type in tamagui in your tamagui.config.ts
, like so:
const config = createTamagui({...yourConfig,})type AppConfig = typeof configdeclare module 'tamagui' {interface TamaguiCustomConfig extends AppConfig {}// add this line:type ThemeFallbackValue = {}}
This will change all special style properties that map to token/theme values to not accept strings or numbers. If you want to make exceptions, you can then add back in any general types by overriding one of the following: SizeTokens
, FontTokens
, FontSizeTokens
, FontLineHeightTokens
, FontWeightTokens
, FontLetterSpacingTokens
, FontStyleTokens
, FontTransformTokens
, SpaceTokens
, ColorTokens
, and ZIndexTokens
.
Themes
Themes live one level below tokens. Tokens are your variables, where themes use those tokens to create consistent, generic properties that you then typically use in shareable components. Themes should generally only deal with colors.
Tamagui components in general expect a set of theme keys to be defined like the following, but you can deviate if you create your own design system. See the source for the website for a fuller example.
const light = {background: '#fff',backgroundHover: tokens.color.gray2,backgroundPress: tokens.color.gray4,backgroundFocus: tokens.color.gray5,backgroundTransparent: tokens.color.grayA1,borderColor: tokens.color.gray4,borderColorHover: tokens.color.gray6,borderColorPress: tokens.color.gray12,borderColorFocus: tokens.color.gray11,color: tokens.color.gray10,colorHover: tokens.color.gray9,colorPress: tokens.color.gray8,colorFocus: tokens.color.gray8,shadowColor: tokens.color.grayA4,shadowColorHover: tokens.color.grayA6,shadowColorPress: tokens.color.grayA8,shadowColorFocus: tokens.color.grayA8,...lightColors,}
You don't have to use tokens as your theme values, but if you do they avoid some overhead. With Tamagui, the idea is that bg
, color
, and borderColor
represent the "primary" and clearest colors, and bg2
, color2
etc get more subtle.
To see how it works, here's a snippet from InteractiveFrame
which is the frame component that's used in Button
:
export const InteractiveFrame = styled(XStack, {borderRadius: '$1',paddingVertical: '$2',paddingHorizontal: '$3',backgroundColor: '$background',justifyContent: 'center',alignItems: 'center',cursor: 'pointer',flexWrap: 'nowrap',flexDirection: 'row',flexShrink: 1,hoverStyle: {backgroundColor: '$backgroundHover',},pressStyle: {backgroundColor: '$backgroundPress',},// ...})
Media
For more full docs on media queries, see the useMedia docs page.
Animations
Choose one of @tamagui/animations-css
, @tamagui/animations-react-native
or @tamagui/animations-reanimated
. Note that reanimated
support is not as well supported.
Append a new key animations
to your createTamagui
call:
import { createAnimations } from '@tamagui/animations-react-native'// pass this exported `animations` to your `createTamagui` call:export const animations = createAnimations({bouncy: {damping: 9,mass: 0.9,stiffness: 150,},lazy: {damping: 18,stiffness: 50,},}),})
If you want to have different drivers for each platform, like a CSS driver for the web and react-native for native, simply use the React Native convention of having platform-specific files: add a animations.ts
and animations.native.ts
. Set them up just like above, with matching keys, but importing from their respective driver.
Shorthands
Shorthands are defined directly on your createTamagui
call, rather than being attached to each component, to de-couple shorthands from components. This forces component kits to standardize on the basic react-native style syntax and ensures there's only one set of abbreviations defined ever.
Here's an example of a partial shorthands configuration:
const shorthands = {ac: 'alignContent',ai: 'alignItems',als: 'alignSelf',bblr: 'borderBottomLeftRadius',bbrr: 'borderBottomRightRadius',bc: 'backgroundColor',br: 'borderRadius',btlr: 'borderTopLeftRadius',btrr: 'borderTopRightRadius',f: 'flex',// ...} as constexport default createTamagui({shorthands,})
For a full configuration, see @tamagui/shorthands
Others
Props
mediaQueryDefaultActive
Record<string, boolean>
For the first render, determines which media queries are true (useful for SSR).
cssStyleSeparator
string
What's between each generated CSS style rule. Set as newline to more easily debug outputted CSS.
themeClassNameOnRoot
boolean
When using next-themes or anything that does SSR and attaches the theme class to the HTML tag, set this to true to have the proper CSS theme selectors generate
shouldAddPrefersColorThemes
boolean
Default:
Generates @media queries based on prefers-color-scheme for you if you have light/dark themes.
maxDarkLightNesting
number
Default:
3
(Advanced) On the web, tamagui treats "dark" and "light" themes as special and generates extra CSS to avoid having to re-render the entire page. This CSS relies on specificity hacks that multiply by your sub-themes. This prop sets the maxiumum number of nested dark/light themes you can do. Defaults to 3 for a balance, but can be higher if you nest them deeply.