Universal React UI framework for web and native (v1.144.0+)
This skill provides comprehensive guidance for building cross-platform React applications using Tamagui's styling system, component library, Bento premium components, and Takeout starter kit.
Overview
Tamagui is a universal UI framework that lets you write once and deploy to both web and React Native with optimal performance. It features:
-
Unified styling API -
styled()function with design tokens, variants, and responsive patterns -
Optimizing compiler - Extracts styles to atomic CSS at build time for maximum performance
-
Theme system - 12-step color scales with automatic light/dark mode support
-
Cross-platform components - Button, Dialog, Sheet, Input, Select, Tabs, and more
-
Premium ecosystem - Bento (production forms, tables, lists) + Takeout (full-stack starter with routing, auth, database, real-time sync)
Version: 1.144.0+ Platforms: Web (React), iOS/Android (React Native), Expo License: Open source (MIT) + Bento/Takeout (commercial licenses)
When to Use This Skill
Activate this skill when encountering these triggers:
Core Framework
-
"tamagui", "universal UI", "react native web", "cross-platform UI"
-
"styled()", "design tokens", "$token", "theme tokens"
-
"XStack", "YStack", "ZStack", "Stack components"
-
"useTheme", "useMedia", "responsive design"
-
"@tamagui/*" imports, "@tamagui/core"
-
"createStyledContext", "withStaticProperties"
-
"variant", "variants", "defaultVariants"
Bento Components
-
"bento", "@tamagui/bento", "bento components"
-
"React Hook Form", "react-hook-form", "form validation"
-
"Zod", "zod validation", "form schema"
-
"TanStack Table", "@tanstack/react-table", "data table"
-
"virtualized list", "FlatList", "masonry layout"
Takeout Starter
-
"takeout", "tamagui takeout", "one.js"
-
"file-based routing", "one router", "SSG", "SSR"
-
"Better Auth", "authentication", "OAuth"
-
"Drizzle ORM", "database", "PostgreSQL"
-
"Zero Sync", "real-time sync", "offline-first"
Platform-Specific
-
"animation driver", "CSS animations", "Reanimated"
-
"compiler optimization", "static extraction", "bundle size"
-
"web-only", "native-only", "platform-specific"
Quick Start Patterns
1. Basic Styling with styled()
Create custom components by extending existing ones:
import { View, Text, styled } from '@tamagui/core'
// Simple styled component
export const Card = styled(View, {
padding: '$4',
backgroundColor: '$background',
borderRadius: '$4',
borderWidth: 1,
borderColor: '$borderColor',
})
// With variants
export const Button = styled(View, {
padding: '$3',
borderRadius: '$2',
backgroundColor: '$blue10',
cursor: 'pointer',
variants: {
variant: {
primary: {
backgroundColor: '$blue10',
},
secondary: {
backgroundColor: '$gray5',
},
outlined: {
backgroundColor: 'transparent',
borderWidth: 1,
borderColor: '$borderColor',
},
},
size: {
small: { padding: '$2' },
medium: { padding: '$3' },
large: { padding: '$4' },
},
} as const,
defaultVariants: {
variant: 'primary',
size: 'medium',
},
})
// Usage
<Button variant="outlined" size="large">Click Me</Button>
2. Core Layout Components
Stacks are the foundation of Tamagui layouts:
import { XStack, YStack, ZStack, Text, Button } from 'tamagui'
export function LayoutExample() {
return (
<YStack gap="$4" padding="$4">
{/* Horizontal stack */}
<XStack gap="$2" justifyContent="space-between" alignItems="center">
<Text>Label</Text>
<Button>Action</Button>
</XStack>
{/* Vertical stack with responsive gap */}
<YStack
gap="$4"
$gtSm={{ gap: '$6' }} // Larger gap on small+ screens
>
<Card>Item 1</Card>
<Card>Item 2</Card>
</YStack>
{/* Overlay stack (position: relative) */}
<ZStack width={300} height={200}>
<View backgroundColor="$blue5" fullscreen />
<Text position="absolute" top="$4" left="$4">
Overlay Text
</Text>
</ZStack>
</YStack>
)
}
3. Using Design Tokens
Design tokens provide consistent values across your app:
import { View, Text, createTamagui } from '@tamagui/core'
// Tokens are defined in createTamagui config
const config = createTamagui({
tokens: {
color: {
white: '#fff',
black: '#000',
blue: '#0066cc',
},
space: {
1: 4,
2: 8,
3: 12,
4: 16,
},
size: {
sm: 100,
md: 200,
lg: 300,
},
radius: {
1: 4,
2: 8,
3: 12,
4: 16,
},
},
})
// Use tokens with $ prefix
<View
padding="$4" // Uses space.4 = 16px
backgroundColor="$blue" // Uses color.blue
borderRadius="$3" // Uses radius.3 = 12px
width="$md" // Uses size.md = 200px
/>
// Tokens work in styled()
const StyledCard = styled(View, {
padding: '$4',
margin: '$2',
backgroundColor: '$background',
borderRadius: '$4',
})
4. Theming with 12-Step Color Scales
Tamagui uses a semantic color scale system (1-12) for consistent theming:
import { YStack, Text, Theme } from 'tamagui'
export function ThemeExample() {
return (
<YStack
backgroundColor="$color1" // Subtle background
borderColor="$color6" // Regular border
padding="$4"
>
<Text color="$color12">Heading (highest contrast)</Text>
<Text color="$color11">Body text (high contrast)</Text>
<Text color="$color10">Muted text (low contrast)</Text>
{/* Apply sub-theme */}
<Theme name="blue">
<YStack
backgroundColor="$color9" // Blue solid background
padding="$3"
borderRadius="$2"
>
<Text color="$color1">White text on blue</Text>
</Theme>
</YStack>
</YStack>
)
}
// Dynamic theme switching
import { useTheme } from '@tamagui/core'
export function ThemedComponent() {
const theme = useTheme()
// Access theme values directly
console.log(theme.background.val) // e.g., "#ffffff"
console.log(theme.color11.val) // e.g., "#333333"
return <Text color={theme.color11}>Themed Text</Text>
}
5. Responsive Design with Media Queries
Use media query props for responsive layouts:
import { YStack, Text, useMedia } from 'tamagui'
export function ResponsiveExample() {
const media = useMedia()
return (
<YStack
padding="$4"
$gtSm={{ padding: '$6' }} // > sm breakpoint
$gtMd={{ padding: '$8' }} // > md breakpoint
flexDirection="column"
$gtLg={{ flexDirection: 'row' }} // Row layout on large screens
>
<Text
fontSize="$4"
$gtSm={{ fontSize: '$5' }}
$gtMd={{ fontSize: '$6' }}
>
Responsive Text
</Text>
{/* Conditional rendering based on media query */}
{media.gtMd && <Text>Only on medium+ screens</Text>}
</YStack>
)
}
// Configure media queries in createTamagui
const config = createTamagui({
media: {
xs: { maxWidth: 660 },
sm: { maxWidth: 800 },
md: { maxWidth: 1020 },
lg: { maxWidth: 1280 },
xl: { maxWidth: 1420 },
xxl: { maxWidth: 1600 },
gtXs: { minWidth: 660 + 1 },
gtSm: { minWidth: 800 + 1 },
gtMd: { minWidth: 1020 + 1 },
gtLg: { minWidth: 1280 + 1 },
},
})
6. Animations with enterStyle/exitStyle
Add smooth animations to any component:
import { YStack, Button, AnimatePresence } from 'tamagui'
import { useState } from 'react'
export function AnimationExample() {
const [show, setShow] = useState(false)
return (
<YStack gap="$4">
<Button onPress={() => setShow(!show)}>
Toggle
</Button>
<AnimatePresence>
{show && (
<YStack
key="animated-box"
animation="quick"
enterStyle={{
opacity: 0,
y: -20,
scale: 0.9,
}}
exitStyle={{
opacity: 0,
y: 20,
scale: 0.9,
}}
opacity={1}
y={0}
scale={1}
backgroundColor="$blue5"
padding="$4"
borderRadius="$4"
>
<Text>Animated Content</Text>
</YStack>
)}
</AnimatePresence>
</YStack>
)
}
// Define animations in createTamagui
import { createAnimations } from '@tamagui/animations-react-native'
const animations = createAnimations({
quick: {
type: 'spring',
damping: 20,
mass: 1.2,
stiffness: 250,
},
bouncy: {
type: 'spring',
damping: 10,
mass: 0.9,
stiffness: 100,
},
})
7. Compound Components with createStyledContext
Build cohesive component families:
import { createStyledContext, styled, View, Text } from '@tamagui/core'
import { withStaticProperties } from '@tamagui/helpers'
// Define context
const CardContext = createStyledContext({
size: '$4' as any,
})
// Create base component
const CardFrame = styled(View, {
backgroundColor: '$background',
borderRadius: '$4',
borderWidth: 1,
borderColor: '$borderColor',
context: CardContext,
variants: {
size: {
small: { padding: '$3' },
medium: { padding: '$4' },
large: { padding: '$6' },
},
} as const,
})
// Create child components that consume context
const CardTitle = styled(Text, {
context: CardContext,
fontWeight: 'bold',
variants: {
size: {
small: { fontSize: '$4' },
medium: { fontSize: '$5' },
large: { fontSize: '$6' },
},
} as const,
})
const CardDescription = styled(Text, {
context: CardContext,
color: '$color11',
variants: {
size: {
small: { fontSize: '$3' },
medium: { fontSize: '$4' },
large: { fontSize: '$5' },
},
} as const,
})
// Compose with static properties
export const Card = withStaticProperties(CardFrame, {
Title: CardTitle,
Description: CardDescription,
})
// Usage - size cascades to all children
<Card size="large">
<Card.Title>Large Card</Card.Title>
<Card.Description>
All text automatically sized large
</Card.Description>
</Card>
8. Form Component Example
import { Button, Dialog, Input, Label, XStack, YStack } from 'tamagui'
export function LoginForm() {
return (
<YStack gap="$4" padding="$4">
<YStack gap="$2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
placeholder="email@example.com"
autoCapitalize="none"
keyboardType="email-address"
/>
</YStack>
<YStack gap="$2">
<Label htmlFor="password">Password</Label>
<Input
id="password"
secureTextEntry
placeholder="••••••••"
/>
</YStack>
<XStack gap="$2" justifyContent="flex-end">
<Button variant="outlined">Cancel</Button>
<Button theme="blue">Sign In</Button>
</XStack>
</YStack>
)
}
9. Dialog Pattern
import { Button, Dialog, XStack, YStack, H2, Paragraph } from 'tamagui'
export function DialogExample() { return (
<Dialog.Portal>
<Dialog.Overlay
key="overlay"
animation="quick"
opacity={0.5}
enterStyle={{ opacity: 0 }}
exitStyle={{ opacity: 0 }}
/>
<Dialog.Content
bordered
elevate
key="content"
animation={['quick', { opacity: { overshootClamping: true } }]}
enterStyle={{ x: 0, y: -20, opacity: 0, scale: 0.9 }}
exitStyle={{ x: 0, y: 10, opacity: 0, scale: 0.95 }}
gap="$4"
>
<span class="token tag