code/tamagui.dev/data/docs/core/configuration.mdx
Configuration in Tamagui controls things like tokens, themes, media queries, animations, shorthands, and settings.
First, create a tamagui.config.ts file.
npm install @tamagui/config
import { defaultConfig } from '@tamagui/config/v5'
import { createTamagui } from 'tamagui'
export const config = createTamagui({
...defaultConfig,
media: {
...defaultConfig.media,
// add your own media queries here, if wanted
},
})
type OurConfig = typeof config
declare module 'tamagui' {
interface TamaguiCustomConfig extends OurConfig {}
}
We find most people are best off with this as it saves a lot of time, shares shorthands with Tailwind, and has a lot of refinement. For more on our default configuration, go to the next page.
Here's an example of a stripped down configuration to get a feel for the most common concepts:
import { createTamagui, getConfig } from 'tamagui'
export const config = createTamagui({
// tokens work like CSS Variables (and compile to them on the web)
// accessible from anywhere, never changing dynamically:
tokens: {
// width="$sm"
size: { sm: 8, md: 12, lg: 20 },
// margin="$sm"
space: { sm: 4, md: 8, lg: 12 },
// radius="$none"
radius: { none: 0, sm: 3 },
color: { white: '#fff', black: '#000' },
},
// themes are like CSS Variables that you can change anywhere in the tree
// you use <Theme name="light" /> to change the theme
themes: {
light: {
bg: '#f2f2f2',
color: '#000',
},
dark: {
bg: '#111',
color: '#fff',
},
// sub-themes are a powerful feature of tamagui, explained later in the docs
// user theme like <Theme name="dark"><Theme name="blue">
// or just <Theme name="dark_blue">
dark_blue: {
bg: 'darkblue',
color: '#fff',
},
},
// media query definitions can be used as style props or with the useMedia hook
// but also are added to "group styles", which work like Container Queries from CSS
media: {
sm: { maxWidth: 860 },
gtSm: { minWidth: 860 + 1 },
short: { maxHeight: 820 },
hoverable: { hover: 'hover' },
touchable: { pointer: 'coarse' },
},
shorthands: {
// <View px={20} />
px: 'paddingHorizontal',
},
// there are more settings, explained below:
settings: {
disableSSR: true,
allowedStyleValues: 'somewhat-strict-web',
},
})
// now, make your types flow nicely back to your `tamagui` import:
type OurConfig = typeof config
declare module 'tamagui' {
interface TamaguiCustomConfig extends OurConfig {}
}
Finally, pass your config export to a <TamaguiProvider /> at the root of
your app:
import { TamaguiProvider, View } from 'tamagui'
import { config } from './tamagui.config.ts'
export default () => (
<TamaguiProvider config={config}>
<View margin="$sm" />
</TamaguiProvider>
)
You're all set up!
<Notice> To avoid issues with hot reloading and circular imports, make sure to only import your `tamagui.config.ts` once near the root of your app. If you need to access the configuration in other files, you generally do this just by using props on your styled components, or with hooks like `useMedia` or `useTheme`. We do have some getters like `getTokenValue`, and if you really need to access the full configuration object (though we don't find it necessary in most cases), you can use `getConfig`. </Notice>Let's start with an example of a more complete tamagui.config.ts:
import { createFont, createTamagui, createTokens, isWeb } from 'tamagui'
// 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 systemFont = createFont({
family: isWeb ? 'Helvetica, Arial, sans-serif' : 'System',
size: {
1: 12,
2: 14,
3: 15,
},
lineHeight: {
// 1 will be 22
2: 22,
},
weight: {
1: '300',
// 2 will be 300
3: '600',
},
letterSpacing: {
1: 0,
2: -1,
// 3 will be -1
},
// (native only) swaps out fonts by face/style
face: {
300: { normal: 'InterLight', italic: 'InterItalic' },
600: { normal: 'InterBold' },
},
})
// Set up tokens
// The keys can be whatever you want, but if using `tamagui` you'll want 1-10:
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: {
heading: systemFont,
body: systemFont,
},
tokens,
themes: {
light: {
bg: '#f2f2f2',
color: tokens.color.black,
},
dark: {
bg: '#111',
color: tokens.color.white,
},
},
media: {
sm: { maxWidth: 860 },
gtSm: { minWidth: 860 + 1 },
short: { maxHeight: 820 },
hoverable: { hover: 'hover' },
touchable: { 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 end
shorthands: {
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 its 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 tamagui
declare module 'tamagui' {
interface TamaguiCustomConfig extends AppConfig {}
// if you want types for named group styling props (e.g. $group-card-hover),
// define your group names here:
interface TypeOverride {
groupNames(): 'card' | 'header' | 'sidebar'
}
}
export default config
The createTamagui function receives a configuration object with properties:
animations: Configurable animation drivers.media: Cross-platform, typed
media queries.themes: Define themes to style contextually
anywhere in the tree, much like CSS variables.tokens: Your base tokens are much like static CSS variables.settings: Many options for strictness and style behavior.shorthands: Define shorter names for any style property.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 token
large: 40,
},
space: {
small: 10,
medium: 20,
true: 20, // same goes for space and other token categories
large: 30,
},
})
With your config set up, import it near the root of your app and pass it to
TamaguiProvider:
import { TamaguiProvider } from 'tamagui'
import { config } from './tamagui.config'
export default function App() {
return (
<TamaguiProvider config={config} defaultTheme="light">
<AppContents />
</TamaguiProvider>
)
}
TamaguiProvider accepts a few properties:
<PropsTable
data={[
{
name: 'defaultTheme',
required: true,
type: 'string',
description: The initial top level theme.,
},
{
name: 'disableInjectCSS',
required: false,
type: 'boolean',
description: 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.,
},
{
name: 'insets',
required: false,
type: '{ top?: number; bottom?: number; left?: number; right?: number }',
description: Safe area insets for iOS. Required for proper vertical Slider behavior on iOS. Pass the value from useSafeAreaInsets() hook.,
},
]}
/>
By default, Tamagui injects the CSS for your configuration on the client-side
into document.head, but you probably will want something better for
production. To do this, pass true to disableInjectCSS on TamaguiProvider, and
then do one of the following three options:
If your framework has server-side layouts, you can just render it inline:
import { config } from './tamagui.config'
export default () => (
<html>
<head>
<style
dangerouslySetInnerHTML={{
__html: config.getCSS(),
}}
/>
</head>
<body>
<Slot />
</body>
</html>
)
To optimize a bit more so you share a single CSS file between all pages, you can
use one of our bundler plugins' outputCSS setting, like so:
import { tamaguiPlugin } from '@tamagui/vite-plugin'
export default {
plugins: [
tamaguiPlugin({
config: './src/tamagui.config.ts',
outputCSS: './src/tamagui.generated.css',
}),
],
}
And then you'll want to import the resulting tamagui.generated.css into your app.
As final option, you can also generate it yourself with the CLI. First create a
tamagui.build.ts:
import type { TamaguiBuildOptions } from 'tamagui'
export default {
components: ['tamagui'],
config: './config/tamagui.config.ts',
outputCSS: './tamagui.generated.css',
} satisfies TamaguiBuildOptions
And then run:
npx @tamagui/cli generate
See the CLI Guide for more information on the
generate command and other CLI tools.
Tokens are inspired by the Theme UI spec. They are mapped to CSS variables at build time. You can read about them in more depth on the tokens page.
The font tokens are a bit special and are created with createFont:
import { isWeb } from 'tamagui'
const interFont = createFont({
family: isWeb ? 'Inter, Helvetica, Arial, sans-serif' : 'Inter',
size: {
1: 12,
2: 14,
3: 15,
// ...
},
lineHeight: {
1: 17,
2: 22,
3: 25,
// ...
},
weight: {
4: '300',
6: '600',
},
letterSpacing: {
4: 0,
8: -1,
},
// because android handles fonts differently, you need to map the weight
// to the actual name of the font in the font-file
// you can get the name with `otfinfo`: otfinfo --family Inter.ttf
face: {
400: { normal: 'Inter', italic: 'Inter-Italic' },
500: { normal: 'InterBold', italic: 'InterBold-Italic' },
},
})
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.
If you are using a custom font for native, you need to load your fonts for React Native to recognize them. Tamagui doesn't really touch this area, instead you'll use Expo or React Native directly, something like this:
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 />
}
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 createTamagui
const 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="$body" 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.
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
settings below.
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 config = createTamagui({
themes: {
light: {
background: '#fff',
backgroundHover: tokens.color.gray2,
// ...
color: tokens.color.gray10,
colorHover: tokens.color.gray9,
colorPress: tokens.color.gray8,
// ...
color1: tokens.color.gray1,
},
dark: {
background: '#000',
// ... matching the properties for light ^
},
},
// ... the rest of your configuration
})
Passing tokens to themes will be smart about sharing CSS, but is not required.
You can then access theme values for any style value, either through styled() or through a Styled Component like View or Text:
const P = styled(Text, {
color: '$color12'
})
// or directly
<Text color="$color11" />
One of the more powerful features in Tamagui is nesting themes, you just define them like so:
const config = createTamagui({
themes: {
light: {
color1: '#fff',
// ...
},
dark: {
color1: '#000',
// ...
},
light_alert: {
background: '#e6ebbd',
},
dark_alert: {
background: '#3e3f33',
},
},
// ... the rest of your configuration
})
Themes work just like CSS variables, they can be changed anywhere in the tree.
Sub-themes can be subsets of parent themes likewise as their values will fall
back to the parent theme. They can also be nested as deeply as you like - so
even light_alert_subtle is possible.
For more on themes, see the themes docs, and more advanced theme building library.
For more full docs on media queries, see the useMedia docs page.
Tamagui supports four animation drivers that can be swapped out per-platform:
@tamagui/animations-css - CSS transition based
animations (lightest)@tamagui/animations-react-native -
React Native Animated@tamagui/animations-reanimated -
Reanimated for advanced animations@tamagui/animations-motion - Motion with
WAAPI for webSee the Animations documentation for a full comparison and guide on choosing the right 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,
},
})
You can use different drivers per-platform - see the Platform-Specific Drivers section.
Shorthands are defined on createTamagui. Here's an example of a partial
shorthands configuration:
// the as const ensures types work with the optional `onlyAllowShorthands` option
const shorthands = {
ac: 'alignContent',
ai: 'alignItems',
als: 'alignSelf',
bblr: 'borderBottomLeftRadius',
bbrr: 'borderBottomRightRadius',
bg: 'backgroundColor',
br: 'borderRadius',
btlr: 'borderTopLeftRadius',
btrr: 'borderTopRightRadius',
f: 'flex',
// ...
} as const
export default createTamagui({
shorthands,
})
Which will enable usage like:
<View br="$myToken" />
where br expands into borderRadius.
You can pass a settings object to createTamagui:
<PropsTable
data={[
{
name: disableSSR,
type: boolean,
description: 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. See the [Server Rendering](/docs/core/server-rendering) docs for more details on SSR, the ClientOnly component, and related hooks.,
},
{
name: defaultFont,
required: false,
type: string,
description: 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.,
},
{
name: 'mediaQueryDefaultActive',
required: false,
type: 'Record<string, boolean>',
description: For the first render, determines which media queries are true (useful for SSR).,
},
{
name: 'addThemeClassName',
required: false,
type: "false | 'html' | 'body'",
default: false,
description: If you want to style your <body> tag to use theme CSS variables on web, you must place the theme className onto the body element or above. This will do so. If disabled, Tamagui will place the className onto the element rendered by the TamaguiProvider,
},
{
name: 'disableRootThemeClass',
required: false,
type: 'boolean',
description:
'On web, TamaguiProvider will add your top level theme className to the root of your document, the html tag. This lets you do things like style your body tag using Tamagui CSS variables that are generated by themes. If disabled, the theme className will instead by applied to the span inside the TamaguiProvider directly.',
},
{
name: 'selectionStyles',
required: false,
type: '(theme) => ({ backgroundColor: Variable | string; color: Variable | string })',
default: false,
description: On the web, will generate ::selection styles for text selection.,
},
{
name: 'shouldAddPrefersColorThemes',
required: false,
type: 'boolean',
default: true,
description: Generates @media queries based on prefers-color-scheme for you if you have light/dark themes, causes larger total CSS sizes.,
},
{
name: 'onlyAllowShorthands',
required: false,
type: 'boolean',
default: false,
description: Will remove the type for the long-form versions of any shorthands you define.,
},
{
name: allowedStyleValues,
required: false,
type: 'AllowedStyleValuesSetting',
description: Set up allowed values on style props, this is only a type-level validation (see below).,
},
{
name: autocompleteSpecificTokens,
required: false,
type: boolean | 'except-special',
description: Set up if "specific tokens" ($color.name) are added to the types where tokens are allowed (see below).,
},
{
name: 'fastSchemeChange',
required: false,
type: 'boolean',
default: false,
description: On iOS, this enables a mode where Tamagui returns color values using DynamicColorIOS significantly speeding up dark/light re-renders. Note: When enabled you must make sure the defaultTheme you set on TamaguiProvider matches the current system color scheme.,
},
{
name: 'webContainerType',
required: false,
type: 'string',
default: 'inline-size',
description: 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".,
},
]}
/>
allowedStyleValuesfalse (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:
space/size: string% or numbersradius: numberzIndex: numbercolor: named colors or rgba/hsla stringssomewhat-strict-web: same as somewhat-strict but allows for web-specific
tokenstype AllowedValueSetting =
| boolean
| 'strict'
| 'somewhat-strict'
| 'strict-web'
| 'somewhat-strict-web'
type AllowedStyleValuesSetting =
| AllowedValueSetting
| {
space?: AllowedValueSetting
size?: AllowedValueSetting
radius?: AllowedValueSetting
zIndex?: AllowedValueSetting
color?: AllowedValueSetting
}
autocompleteSpecificTokensThe 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.
A few things in Tamagui can be configured via environment variables. Doing it this way lets us avoid extra code being shipped to the client, as your bundler can tree shake the unused code easily when using environment variables. You should be sure to either use the Vite or Webpack Tamagui plugins, or configure your bundler to define the environment variables for tree shaking. On native, it is not a concern as the extra size is minimal.
Production Optimization - Theme JS can grow to 20KB or more. Since Tamagui can hydrate themes from CSS variables, you can remove the themes JS from your client bundle for better Lighthouse scores.
This works because Tamagui generates CSS variables for all theme values. On the client, it reads these from the DOM instead of needing the JS object.
<Notice> This optimization only applies to SSR web apps (Next.js, One, Vite SSR). It doesn't apply to Metro/Expo or static sites. Note that v5 themes are already more optimized than v4, so this is mainly relevant if chasing the last few Lighthouse points. </Notice>import { defaultConfig, themes } from '@tamagui/config/v5'
import { createTamagui } from 'tamagui'
export const config = createTamagui({
...defaultConfig,
// only load themes on server - client hydrates from CSS
// for non-One Vite apps, use import.meta.env.SSR instead
themes: process.env.VITE_ENVIRONMENT === 'client' ? ({} as typeof themes) : themes,
})