Tamagui
Cross-platform design system with optimized style compilation.
Search across all documentation pages
Cross-platform design system with optimized style compilation.
Tamagui is a universal design system for React Native (and web): typed tokens, styled primitives (Stack, Text, Button), and a compiler that flattens style props into optimized objects. On Expo SDK 57, it fits teams that want component + token discipline beyond utility classes - with performance tuned for list-heavy apps.
Quick-reference recipe card - copy-paste ready.
npx create-expo-app@latest MyTamaguiApp --template blank-typescript
cd MyTamaguiApp
npx expo install tamagui @tamagui/config @tamagui/core react-native-reanimated react-native-safe-area-context// tamagui.config.ts
import { config as defaultConfig } from "@tamagui/config";
import { createTamagui } from "tamagui";
export const config = createTamagui({
...defaultConfig,
tokens: {
...defaultConfig.tokens,
color: {
...defaultConfig.tokens.color,
brand: "#2563eb",
brandMuted: "#93c5fd",
},
},
themes: {
...defaultConfig.themes,
light: {
...defaultConfig.themes.light,
brand: "#2563eb",
},
dark: {
...defaultConfig.themes.dark,
brand: "#60a5fa",
},
},
});
export type AppConfig = typeof config;
declare module "tamagui" {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface TamaguiCustomConfig extends AppConfig {}
}// App.tsx
import { TamaguiProvider, Theme, YStack, Text, Button } from "tamagui";
import { useColorScheme } from "react-native";
import { config } from "./tamagui.config";
export default function App() {
const scheme = useColorScheme() ?? "light";
return (
<TamaguiProvider config={config} defaultTheme={scheme}>
<Theme name={scheme}>
<YStack flex={1} padding="$4" backgroundColor="$background" gap="$3">
<Text fontSize="$6" fontWeight="700" color="$color">
Tamagui on Expo
</Text>
<Button theme="active" backgroundColor="$brand" color="white">
Continue
</Button>
</YStack>
</Theme>
</TamaguiProvider>
);
}{
"expo": {
"plugins": ["react-native-reanimated/plugin"]
}
}When to reach for this:
size="$4", color="$brand") with compile-time validation.Button, Card) rather than only utility classes.Product card with variants, dark theme, and a memoized list row built on Tamagui tokens.
// components/ProductCard.tsx
import { Card, H4, Paragraph, XStack, YStack, styled } from "tamagui";
const Badge = styled(Paragraph, {
name: "Badge",
fontSize: "$1",
fontWeight: "700",
paddingHorizontal: "$2",
paddingVertical: "$1",
borderRadius: "$10",
variants: {
tone: {
sale: { backgroundColor: "$red4", color: "$red11" },
new: { backgroundColor: "$green4", color: "$green11" },
},
} as const,
defaultVariants: { tone: "new" },
});
type Props = {
title: string;
price: string;
badge?: "sale" | "new";
};
export function ProductCard({ title, price, badge = "new" }: Props) {
return (
<Card elevate bordered padding="$4" backgroundColor="$background">
<YStack gap="$2">
<XStack justifyContent="space-between" alignItems="center">
<H4 color="$color" numberOfLines={1}>
{title}
</H4>
{badge && <Badge tone={badge}>{badge === "sale" ? "SALE" : "NEW"}</Badge>}
</XStack>
<Paragraph color="$color11" fontSize="$5" fontWeight="600">
{price}
</Paragraph>
</YStack>
</Card>
);
}// screens/CatalogScreen.tsx
import { FlatList } from "react-native";
import { YStack, Text } from "tamagui";
import { ProductCard } from "../components/ProductCard";
const PRODUCTS = [
{ id: "1", title: "Trail Runner Pro", price: "$129", badge: "new" as const },
{ id: "2", title: "Merino Base Layer", price: "$79", badge: "sale" as const },
{ id: "3", title: "Softshell Jacket", price: "$199", badge: "new" as const },
];
export function CatalogScreen() {
return (
<YStack flex={1} padding="$4" backgroundColor="$background" gap="$3">
<Text fontSize="$8" fontWeight="800" color="$color">
Catalog
</Text>
<FlatList
data={PRODUCTS}
keyExtractor={(item) => item.id}
ItemSeparatorComponent={() => <YStack height="$3" />}
renderItem={({ item }) => (
<ProductCard title={item.title} price={item.price} badge={item.badge} />
)}
/>
</YStack>
);
}// app/_layout.tsx
import { TamaguiProvider, Theme } from "tamagui";
import { Slot } from "expo-router";
import { useColorScheme } from "react-native";
import { config } from "../tamagui.config";
export default function RootLayout() {
const scheme = useColorScheme() ?? "light";
return (
<TamaguiProvider config={config} defaultTheme={scheme}>
<Theme name={scheme}>
<Slot />
</Theme>
</TamaguiProvider>
);
}What this demonstrates:
$ token shorthand - $4 resolves to spacing token 4 from config.styled() with variants - tone="sale" maps to theme-aware colors, not inline hex.Theme name={scheme} swaps light/dark theme objects from tamagui.config.ts.Card elevate bordered - Tamagui shorthand props compile to platform-appropriate elevation.createTamagui merges your tokens, themes, fonts, and media into one config object.TamaguiProvider supplies config and active theme to the React tree.padding="$4" to numeric values at compile/runtime and flatten to RN style objects.light, dark). Components reference $color, $background, not hard-coded hex.@tamagui/compiler or Babel plugin) can further optimize static trees - evaluate when bundle size matters.| Layer | Example | Purpose |
|---|---|---|
tokens.color.brand | #2563eb | Raw palette |
themes.light.brand | #2563eb | Theme-specific role |
| Component prop | backgroundColor="$brand" | Consumption in JSX |
declare module "tamagui" {
interface TamaguiCustomConfig extends AppConfig {}
}$ tokens and variant names.as const on variants objects so tone is "sale" | "new", not string.AppConfig from tamagui.config.ts for Storybook decorators that need typed themes.Some Tamagui optional packages need explicit resolution. If Metro fails on @tamagui/* subpaths:
// metro.config.js
const { getDefaultConfig } = require("expo/metro-config");
const config = getDefaultConfig(__dirname);
config.resolver.unstable_enablePackageExports = true;
module.exports = config;Run npx expo start --clear after adding Tamagui to an existing project.
Missing TamaguiProvider - $ tokens resolve to undefined styles. Fix: Wrap root layout, same placement as other theme providers.
Theme name mismatch - Theme name="dark" without a dark key in themes. Fix: Fork @tamagui/config themes or define both light and dark.
Mixing Tamagui and raw View - Spacing rhythm breaks. Fix: Use YStack/XStack for layout islands; migrate incrementally.
Inline hex on Tamagui components - Defeats theming. Fix: Add token + theme entry, reference $brand.
Reanimated plugin order - Reanimated Babel plugin must be last in babel.config.js. Fix: Follow Expo Reanimated docs when enabling animations alongside Tamagui.
Web-only props on native - Some shorthand props target web CSS. Fix: Test on both platforms; use Platform.select in custom styled components when needed.
| Alternative | Use When | Don't Use When |
|---|---|---|
| Tamagui | Token-typed components + web sharing | Team wants only Tailwind strings |
| NativeWind | Utility-class ergonomics | You need first-class variant APIs on primitives |
| React Native Paper | Material Design out of the box | Heavily custom brand language |
| StyleSheet + tokens | Minimal dependencies | Large DS with many variant matrices |
| @expo/ui | Native SwiftUI/Compose chrome | Custom design language unrelated to platform widgets |
Yes. Install tamagui, @tamagui/config, and react-native-reanimated via npx expo install. Wrap the app in TamaguiProvider and load tamagui.config.ts at the root layout.
$4 references the space token 4. $color references the active theme's color role. It is Tamagui's shorthand for token lookup - not a string literal.
Spread defaultConfig from @tamagui/config, override tokens and themes, and keep createTamagui types via module augmentation. Do not copy the entire default theme blindly - trim unused tokens to reduce bundle size.
NativeWind maps Tailwind classes to styles. Tamagui provides styled components + tokens with a variant system. Teams allergic to className often prefer Tamagui; teams from web Tailwind often prefer NativeWind.
Define light and dark in themes, wrap with <Theme name={scheme}>, and drive scheme from useColorScheme. See Dark Mode & Color Schemes.
Yes. Keep renderItem lean; extract ProductCard as a separate component. Avoid creating new style objects inline in the render callback.
Tamagui components forward props to RN primitives - set accessibilityRole, accessibilityLabel, and test with VoiceOver/TalkBack. See Accessibility Basics.
Stack versions: This page was written for React 19.2.3, React Native 0.86.0, and Expo SDK 57 (
expo~57.0.4).
Reviewed by Chris St. John·Last updated Jul 19, 2026