Configuration
Note that you don't have to set up a design system with createTamagui
at all,
and that we have a variety of helpful presets if you'd like some of the upsides
without having to set it up yourself.
To start, create a tamagui.config.ts
in the root of your app. It should only
be imported by the top-level file that has all your root providers to ensure no
cycles exist in your imports.
Quick Start
You can use @tamagui/config
which provides a complete config out of the box,
the same one this site uses.
tamagui.config.ts
import { config } from '@tamagui/config/v3'import { createTamagui } from 'tamagui' // or '@tamagui/core'const appConfig = createTamagui(config)export type AppConfig = typeof appConfigdeclare module 'tamagui' {// or '@tamagui/core'// overrides TamaguiCustomConfig so your custom types// work everywhere you import `tamagui`interface TamaguiCustomConfig extends AppConfig {}}export default appConfig
Then add Inter font to your index.html
head for web:
index.html
<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet">
or App.tsx
if using Expo:
App.tsx
import { useFonts } from 'expo-font'export default function App() {const [loaded] = useFonts({Inter: require('@tamagui/font-inter/otf/Inter-Medium.otf'),InterBold: require('@tamagui/font-inter/otf/Inter-Bold.otf'),})if (!loaded) {return null}return <>...</>}
See Custom Font Family on Native
under Font tokens for more info on Expo font loading.
Inter fonts for the body
, heading
, and mono
are defined in @tamagui/config/v3
, so you'll want to load them, but you can also easily change those definitions by defining your own fonts.
You can also import parts of @tamagui/config/v3
separately, swapping out any
piece for your own. The v3 config defaults to the CSS animation driver on the
web and the built-in Animated driver on React Native.
Throughout this guide keep in mind that you should import from just one "ui"
package - whether it's @tamagui/core
or tamagui
or it's your custom one.
A gist for custom theme suites
The themes in the config above are specific to the Tamagui site.
We've set up a themes example gistÂ
which has a full built-up theme suite which you can pass to createTamagui({ themes })
. It's large,
so you may want to put it in a themes.ts
file next to your tamagui.config.ts
and import it.
From scratch
Let's start with an example of a complete tamagui.config.ts
. Note that keys
used here can be customized to your liking, and remember that they combine with
your shorthands to give you a syntax like this:
<XStack bg="$red" w="$2" h="$1" />
tamagui.config.ts
// @tamagui/core doesn't include `createMedia` so that it can avoid// a dependency on react-native. If you are using tamagui, you can// import createMedia from there directly and avoid this line:import { createMedia } from '@tamagui/react-native-media-driver'import { createFont, createTamagui, createTokens } from 'tamagui'// Create a font:// To work with the tamagui UI kit styled components (which is optional)// you'd want the keys used for `size`, `lineHeight`, `weight` and// `letterSpacing` to be consistent. The `createFont` function// will fill-in any missing values if `lineHeight`, `weight` or// `letterSpacing` are subsets of `size`.const interFont = createFont({family: 'Inter, Helvetica, Arial, sans-serif',size: {1: 12,2: 14,3: 15,},lineHeight: {// 1 will be 222: 22,},weight: {1: '300',// 2 will be 3003: '600',},letterSpacing: {1: 0,2: -1,// 3 will be -1},// (native only) swaps out fonts by face/styleface: {300: { normal: 'InterLight', italic: 'InterItalic' },600: { normal: 'InterBold' },},})// Set up our tokens// The keys can be whatever you want, but we do recommend keeping them// consistent across the different token categories and intended for// usage together to make nice designs - eg for a Button to use.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,// For more on themes, see the Themes pagethemes: {light: {bg: '#f2f2f2',color: tokens.color.black,},dark: {bg: '#111',color: tokens.color.white,},},// 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 you are going to target React Native, use `createMedia` (it's an identity// function on web so you can import it there without concern).media: createMedia({sm: { maxWidth: 860 },gtSm: { minWidth: 860 + 1 },short: { maxHeight: 820 },hoverNone: { hover: 'none' },pointerCoarse: { pointer: 'coarse' },}),// Shorthands// Adds <View m={10} /> to <View margin={10} />// See Settings section on this page to only allow shorthands// Be sure to have `as const` at the endshorthands: {px: 'paddingHorizontal',f: 'flex',m: 'margin',w: 'width',} as const,// Change the default props for any styled() component with a name.// We are discouraging the use of this and have deprecated it, prefer to use// styled() on any component to change it's styles.defaultProps: {Text: {color: 'green'},},})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 {}// if you want types for group styling props, define them like so:interface TypeOverride {groupNames(): 'a' | 'b' | 'c'}}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
: Deprecated, use styled() on any component to change it's styles. ~~For more advanced uses, you can override all namedstyled()
components initial values.~~onlyAllowShorthands
: When set to true will ensure that if you define shorthands, they override existing long-form style props.
On Android you need to set the face
option in createFont
or else fonts won't pick up different weights, due to a React Native restriction.
Note, for tamagui
(not core), it expects you to define a true
token that
maps to your default size, this way it knows what token to use by default. So
you'd do something like this:
export const tokens = createTokens({size: {small: 20,medium: 30,true: 30, // note true = 30 just like medium, your default size tokenlarge: 40,},space: {small: 10,medium: 20,true: 20, // same goes for space and other token categorieslarge: 30,},})
The compiler will parse your tamagui.config.ts file at build-time. For this reason, we recommend 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.
App.tsx
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 useInsertionEffect on load. But if you're setting up SSR you'll want to use getCSS() on the server instead and then turn on this setting.
If you use disableInjectCSS
, you'll need to do something like this from the server:
<head><style dangerouslySetInnerHTML={{ __html: Tamagui.getCSS(), }} /></head>
If you are using Next.js, see the Next.js guide for more information.
Including the reset css
There is an optional CSS reset that helps normalize styling, you can import it into your app like so:
import '@tamagui/core/reset.css'
You will have to turn isCSSEnabled
on if you're using Metro as your web bundler. See Metro guide.
Setup media query driver
@tamagui/core
doesn't provide media capabilities out of the box to native
apps, while on the web it uses native media queries. To enable media queries for
React Native, you need to provide matchMedia implementation:
yarn add @tamagui/react-native-media-driver
import { createMedia } from '@tamagui/react-native-media-driver'export default createTamagui({media: createMedia({xs: { maxWidth: 660 },// ...}),})
Optimizing further (Advanced)
import Tamagui from './tamagui.config'const css = Tamagui.getCSS()// insert into your head
Note that you can optimize this a bit by generating your CSS at build-time and then avoiding generating it on the client. This is covered in the Next.js Guide as it gives a good example of how to put all the pieces together properly, but you can apply it to any other framework.
To do this you would do something like this:
Tamagui.getCSS({// if you are calling getCSS on the server somewhere, you can add this { exclude: 'design-system' }// to your *client* side getCSS call to avoid more work and duplicate CSS generation.// Note that you call getCSS on both the server and client. This is what you'd write on the client.// It will ensure any CSS generated by your first render pass is available.exclude: process.env.NODE_ENV === 'production' ? 'design-system' : null,})
For Next.js there's a nice option to outputCSS
which
will handle emitting a css file for you. But you can also just do this yourself
by calling Tamagui.getCSS() with any other bundler or framework sometime in your
pre-build or on the server and handing it over as CSS.
One final optimization you can make if you are serving getCSS from the server is
setting the environment variable TAMAGUI_DOES_SSR_CSS
to "true"
for the
client bundle only. One gotcha - if you plan on calling mutateTheme
or
addTheme
at some point, set TAMAGUI_DOES_SSR_CSS
to "mutates-themes"
. This
will still give you the benefits of reduced bundle size, but avoids removing the
entirety of the theme generation JS so it can mutate themes at runtime.
Most bundlers will then be able to shake out all the theme-related CSS
generation code from @tamagui/core
, saving ~2kb of minified JS. If you set
outputCSS
option with Next.js, this will be set automatically.
Make sure the style tag you insert with the getCSS
styles is inserted after any react-native-web
style tags to ensure Tamagui overrides those 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,
Troubleshooting
Custom fonts not appearing in Tamagui components? Note the family
definition, on Native it requires a single font name, whereas on web it can include a comma-separated list of fallbacks.
You can use the isWeb
boolean to do this conditionally on createFont
:
createFont({family: isWeb ? 'Custom-Font, Arial, Helvetica, ...' : 'Custom-Font',...rest})
Custom Font Family on Native
You need to load your fonts for React Native to recognize them. Typically this looks something like if using Expo, (or you can follow a React Native guide here ):
App.tsx
import { useFonts } from 'expo-font'function App() {const [loaded] = useFonts({Inter: require("@tamagui/font-inter/otf/Inter-Medium.otf"),InterBold: require("@tamagui/font-inter/otf/Inter-Bold.otf"),});useEffect(() => {if (loaded) {// can hide splash screen here}}, [loaded])if (!loaded) {return null;}return <MyApp />}
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.
Configuring tokens
There are a few settings that control how strict your style values are allowed
to be, which are handled by the settings
option of createTamagui
. See the
type strictness settings below.
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.
const light = {background: '#fff',backgroundHover: tokens.color.gray2,backgroundPress: tokens.color.gray4,backgroundFocus: tokens.color.gray5,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
for CSS transition based animations,
@tamagui/animations-react-native
for React Native Animated based animations,
or @tamagui/animations-moti
for Moti animations. You can
swap them out per-platform as well, so long as you match the keys of the
animations you pass in for each driver.
Add animations
to createTamagui
:
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 using @tamagui/animations-moti
, be sure to add moti
as a dependency as well.
Shorthands
Shorthands are defined on createTamagui
. Here's an example of a partial
shorthands configuration:
// the as const ensures types work with the optional `onlyAllowShorthands` optionconst shorthands = {ac: 'alignContent',ai: 'alignItems',als: 'alignSelf',bblr: 'borderBottomLeftRadius',bbrr: 'borderBottomRightRadius',bg: 'backgroundColor',br: 'borderRadius',btlr: 'borderTopLeftRadius',btrr: 'borderTopRightRadius',f: 'flex',// ...} as constexport default createTamagui({shorthands,})
Which will enable usage like:
<View br="$myToken" />
where br
expands into borderRadius
.
For a full shorthands setup, see the @tamagui/shorthands source .
Settings
You can pass a settings
object to createTamagui
:
Props
disableSSR
boolean
For SSR compatibility on the web, Tamagui will render once with the settings from mediaQueryDefaultActive set for all media queries. Then, it will render again after the initial render using the proper media query values. This is so that the hydrating components will match the server. Setting disableSSR to true will avoid this and instead immediately render using the up to date media state, which is the preferrable behavior for client-side only (SPA) style apps.
defaultFont
string
Map it to the regular key of the font given to createTamagui, so "body" or "heading" make sense as values here. This will ensure this font is the fallback for any views that don't define it.
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
selectionStyles
(theme) => ({ backgroundColor: Variable | string; color: Variable | string })
On the web, will generate ::selection styles for text selection.
shouldAddPrefersColorThemes
boolean
Default:Â
true
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 maximum number of nested dark/light themes you can do. Defaults to 3 for a balance, but can be higher if you nest them deeply.
onlyAllowShorthands
boolean
Will remove the type for the long-form versions of any shorthands you define.
allowedStyleValues
AllowedStyleValuesSetting
Set up allowed values on style props, this is only a type-level validation (see below).
autocompleteSpecificTokens
boolean | 'except-special'
Set up if "specific tokens" ($color.name) are added to the types where tokens are allowed (see below).
mediaPropOrder
boolean
(beta) Will change the behavior of media styles. By default they have a fixed specificity: they always override any $theme- or $platform- styles. With this enabled, media styles will have the same precedence as the theme and platform styles, meaning that the order of the props determines if they override.
fastSchemeChange
boolean
(beta) On iOS, this enables a mode where Tamagui returns color values using DynamicColorIOS significantly speeding up dark/light re-renders. If you use theme inverse or manually change from light to dark without also changing the native color scheme, this will break. We're working on accounting for these last edge cases before removing the beta tag.
webContainerType
string
Default:Â
inline-size
Only for web, when using group styles Tamagui uses container queries. When using a group on a containing element, Tamagui defaults to setting "container-type" in CSS to "inline-size".
Type strictness
allowedStyleValues
false (default)
: allows any string (or number for styles that accept numbers)strict
: only allows tokens for any token-enabled propertiesstrict-web
: same as strict but allows for web-specific tokens like auto/inheritsomewhat-strict
: allow tokens or:- for
space/size
: string% or numbers - for
radius
: number - for
zIndex
: number - for
color
: named colors or rgba/hsla strings
- for
somewhat-strict-web
: same as somewhat-strict but allows for web-specific tokens
type AllowedValueSetting =| boolean| 'strict'| 'somewhat-strict'| 'strict-web'| 'somewhat-strict-web'type AllowedStyleValuesSetting =| AllowedValueSetting| {space?: AllowedValueSettingsize?: AllowedValueSettingradius?: AllowedValueSettingzIndex?: AllowedValueSettingcolor?: AllowedValueSetting}
autocompleteSpecificTokens
The VSCode autocomplete puts specific tokens above the regular ones, which leads to worse DX. If true this setting removes the specific token from types for the defined categories.
If set to except-special
, specific tokens will autocomplete only if they don't
normally use one of the special token groups: space, size, radius, zIndex,
color.