tamagui

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Tamagui Skill

Tamagui 技能指南

Universal React UI framework for web and native with an optimizing compiler.
这是一款适用于Web和原生平台的通用React UI框架,配备优化编译器。

Getting Project-Specific Config

获取项目专属配置

Before writing Tamagui code, get the project's actual configuration:
bash
npx tamagui generate-prompt
This outputs
tamagui-prompt.md
with the project's specific:
  • Design tokens (space, size, radius, color, zIndex)
  • Theme names and hierarchy
  • Available components
  • Media query breakpoints
  • Shorthand properties
  • Font families
Always reference this file for token/theme/media query names rather than guessing or using defaults.

在编写Tamagui代码前,请先获取项目的实际配置:
bash
npx tamagui generate-prompt
该命令会输出
tamagui-prompt.md
文件,包含项目专属的以下内容:
  • 设计令牌(间距、尺寸、圆角、颜色、层级zIndex)
  • 主题名称与层级结构
  • 可用组件
  • 媒体查询断点
  • 简写属性
  • 字体族
请始终参考此文件中的令牌/主题/媒体查询名称,而非自行猜测或使用默认值。

Core Concepts

核心概念

styled() Function

styled() 函数

Create components by extending existing ones:
tsx
import { View, Text, styled } from '@tamagui/core'

const Card = styled(View, {
  padding: '$4',           // use tokens with $
  backgroundColor: '$background',
  borderRadius: '$4',

  variants: {
    size: {
      small: { padding: '$2' },
      large: { padding: '$6' },
    },
    elevated: {
      true: {
        shadowColor: '$shadowColor',
        shadowRadius: 10,
      },
    },
  } as const,  // required for type inference

  defaultVariants: {
    size: 'small',
  },
})

// usage
<Card size="large" elevated />
Key rules:
  • Always use
    as const
    on variants objects
  • Tokens use
    $
    prefix:
    $4
    ,
    $background
    ,
    $color11
  • Prop order matters - later props override earlier ones
  • Variants defined later in the object override earlier ones
通过扩展现有组件来创建新组件:
tsx
import { View, Text, styled } from '@tamagui/core'

const Card = styled(View, {
  padding: '$4',           // 使用$前缀调用令牌
  backgroundColor: '$background',
  borderRadius: '$4',

  variants: {
    size: {
      small: { padding: '$2' },
      large: { padding: '$6' },
    },
    elevated: {
      true: {
        shadowColor: '$shadowColor',
        shadowRadius: 10,
      },
    },
  } as const,  // 必须添加以支持TypeScript类型推断

  defaultVariants: {
    size: 'small',
  },
})

// 使用示例
<Card size="large" elevated />
关键规则:
  • 必须为variants对象添加
    as const
  • 令牌使用$前缀:
    $4
    $background
    $color11
  • 属性顺序会影响优先级,后续属性会覆盖前面的属性
  • 后定义的变体优先级高于先定义的变体

Stack Components

布局栈组件

tsx
import { XStack, YStack, ZStack } from 'tamagui'

// XStack = flexDirection: 'row'
// YStack = flexDirection: 'column'
// ZStack = position: 'relative' with absolute children

<YStack gap="$4" padding="$4">
  <XStack justifyContent="space-between" alignItems="center">
    <Text>Label</Text>
    <Button>Action</Button>
  </XStack>
</YStack>
tsx
import { XStack, YStack, ZStack } from 'tamagui'

// XStack = flexDirection: 'row'
// YStack = flexDirection: 'column'
// ZStack = position: 'relative',子元素为绝对定位

<YStack gap="$4" padding="$4">
  <XStack justifyContent="space-between" alignItems="center">
    <Text>标签</Text>
    <Button>操作</Button>
  </XStack>
</YStack>

Themes

主题系统

Themes nest and combine hierarchically:
tsx
import { Theme } from 'tamagui'

// base theme
<Theme name="dark">
  {/* sub-theme */}
  <Theme name="blue">
    {/* uses dark_blue theme */}
    <Button>Blue button on dark</Button>
  </Theme>
</Theme>

// access theme values
const theme = useTheme()
console.log(theme.background.val)  // actual color value
console.log(theme.color11.val)     // high contrast text
12-step color scale convention:
  • $color1-4
    : backgrounds (subtle to emphasized)
  • $color5-6
    : borders, separators
  • $color7-8
    : hover/active states
  • $color9-10
    : solid backgrounds
  • $color11-12
    : text (low to high contrast)
主题支持嵌套与层级组合:
tsx
import { Theme } from 'tamagui'

// 基础主题
<Theme name="dark">
  {/* 子主题 */}
  <Theme name="blue">
    {/* 使用dark_blue主题 */}
    <Button>深色背景下的蓝色按钮</Button>
  </Theme>
</Theme>

// 获取主题值
const theme = useTheme()
console.log(theme.background.val)  // 实际颜色值
console.log(theme.color11.val)     // 高对比度文本
12阶颜色体系约定:
  • $color1-4
    :背景色(从浅到深)
  • $color5-6
    :边框、分隔线
  • $color7-8
    :悬停/激活状态
  • $color9-10
    :实色背景
  • $color11-12
    :文本(从低对比度到高对比度)

Responsive Styles

响应式样式

Use media query props (check your
tamagui-prompt.md
for actual breakpoint names):
tsx
<YStack
  padding="$4"
  $gtSm={{ padding: '$6' }}   // check your config for actual names
  $gtMd={{ padding: '$8' }}
  flexDirection="column"
  $gtLg={{ flexDirection: 'row' }}
/>

// or with hook
const media = useMedia()
if (media.gtMd) {
  // render for medium+ screens
}
使用媒体查询属性(请查看
tamagui-prompt.md
获取实际断点名称):
tsx
<YStack
  padding="$4"
  $gtSm={{ padding: '$6' }}   // 请根据你的配置使用实际名称
  $gtMd={{ padding: '$8' }}
  flexDirection="column"
  $gtLg={{ flexDirection: 'row' }}
/>

// 或使用钩子
const media = useMedia()
if (media.gtMd) {
  // 为中等及以上屏幕渲染内容
}

Animations

动画实现

tsx
import { AnimatePresence } from 'tamagui'

<AnimatePresence>
  {show && (
    <YStack
      key="modal"  // key required for exit animations
      animation="quick"
      enterStyle={{ opacity: 0, y: -20 }}
      exitStyle={{ opacity: 0, y: 20 }}
      opacity={1}
      y={0}
    />
  )}
</AnimatePresence>
Animation drivers:
  • @tamagui/animations-css
    - web only, CSS transitions
  • @tamagui/animations-react-native
    - native Animated API
  • @tamagui/animations-reanimated
    - best native performance
  • @tamagui/animations-motion
    - spring physics
CSS driver uses easing strings, others support spring physics.

tsx
import { AnimatePresence } from 'tamagui'

<AnimatePresence>
  {show && (
    <YStack
      key="modal"  // 退出动画必须添加key
      animation="quick"
      enterStyle={{ opacity: 0, y: -20 }}
      exitStyle={{ opacity: 0, y: 20 }}
      opacity={1}
      y={0}
    />
  )}
</AnimatePresence>
动画驱动:
  • @tamagui/animations-css
    - 仅支持Web,基于CSS过渡
  • @tamagui/animations-react-native
    - 基于原生Animated API
  • @tamagui/animations-reanimated
    - 原生平台性能最优
  • @tamagui/animations-motion
    - 弹簧物理动画
CSS驱动使用缓动字符串,其他驱动支持弹簧物理效果。

Compound Components

复合组件

Use
createStyledContext
for components that share state:
tsx
import { createStyledContext, styled, View, Text } from '@tamagui/core'
import { withStaticProperties } from '@tamagui/helpers'

const CardContext = createStyledContext({ size: 'medium' as 'small' | 'medium' | 'large' })

const CardFrame = styled(View, {
  context: CardContext,
  padding: '$4',
  backgroundColor: '$background',

  variants: {
    size: {
      small: { padding: '$2' },
      medium: { padding: '$4' },
      large: { padding: '$6' },
    },
  } as const,
})

const CardTitle = styled(Text, {
  context: CardContext,  // inherits size from parent
  fontWeight: 'bold',

  variants: {
    size: {
      small: { fontSize: '$4' },
      medium: { fontSize: '$5' },
      large: { fontSize: '$6' },
    },
  } as const,
})

export const Card = withStaticProperties(CardFrame, {
  Title: CardTitle,
})

// usage - size cascades to children
<Card size="large">
  <Card.Title>Large Title</Card.Title>
</Card>

使用
createStyledContext
创建共享状态的组件:
tsx
import { createStyledContext, styled, View, Text } from '@tamagui/core'
import { withStaticProperties } from '@tamagui/helpers'

const CardContext = createStyledContext({ size: 'medium' as 'small' | 'medium' | 'large' })

const CardFrame = styled(View, {
  context: CardContext,
  padding: '$4',
  backgroundColor: '$background',

  variants: {
    size: {
      small: { padding: '$2' },
      medium: { padding: '$4' },
      large: { padding: '$6' },
    },
  } as const,
})

const CardTitle = styled(Text, {
  context: CardContext,  // 继承父组件的size属性
  fontWeight: 'bold',

  variants: {
    size: {
      small: { fontSize: '$4' },
      medium: { fontSize: '$5' },
      large: { fontSize: '$6' },
    },
  } as const,
})

export const Card = withStaticProperties(CardFrame, {
  Title: CardTitle,
})

// 使用示例 - size属性会传递给子组件
<Card size="large">
  <Card.Title>大标题</Card.Title>
</Card>

Common Patterns

常见模式

Dialog with Adapt (Sheet on Mobile)

自适应对话框(移动端为底部弹窗)

tsx
import { Dialog, Sheet, Adapt, Button } from 'tamagui'

<Dialog>
  <Dialog.Trigger asChild>
    <Button>Open</Button>
  </Dialog.Trigger>

  <Adapt when="sm" platform="touch">
    <Sheet modal dismissOnSnapToBottom>
      <Sheet.Frame padding="$4">
        <Adapt.Contents />
      </Sheet.Frame>
      <Sheet.Overlay />
    </Sheet>
  </Adapt>

  <Dialog.Portal>
    <Dialog.Overlay
      key="overlay"
      animation="quick"
      opacity={0.5}
      enterStyle={{ opacity: 0 }}
      exitStyle={{ opacity: 0 }}
    />
    <Dialog.Content
      key="content"
      animation="quick"
      enterStyle={{ opacity: 0, scale: 0.95 }}
      exitStyle={{ opacity: 0, scale: 0.95 }}
    >
      <Dialog.Title>Title</Dialog.Title>
      <Dialog.Description>Description</Dialog.Description>
      <Dialog.Close asChild>
        <Button>Close</Button>
      </Dialog.Close>
    </Dialog.Content>
  </Dialog.Portal>
</Dialog>
tsx
import { Dialog, Sheet, Adapt, Button } from 'tamagui'

<Dialog>
  <Dialog.Trigger asChild>
    <Button>打开</Button>
  </Dialog.Trigger>

  <Adapt when="sm" platform="touch">
    <Sheet modal dismissOnSnapToBottom>
      <Sheet.Frame padding="$4">
        <Adapt.Contents />
      </Sheet.Frame>
      <Sheet.Overlay />
    </Sheet>
  </Adapt>

  <Dialog.Portal>
    <Dialog.Overlay
      key="overlay"
      animation="quick"
      opacity={0.5}
      enterStyle={{ opacity: 0 }}
      exitStyle={{ opacity: 0 }}
    />
    <Dialog.Content
      key="content"
      animation="quick"
      enterStyle={{ opacity: 0, scale: 0.95 }}
      exitStyle={{ opacity: 0, scale: 0.95 }}
    >
      <Dialog.Title>标题</Dialog.Title>
      <Dialog.Description>描述</Dialog.Description>
      <Dialog.Close asChild>
        <Button>关闭</Button>
      </Dialog.Close>
    </Dialog.Content>
  </Dialog.Portal>
</Dialog>

Form with Input/Label

表单与输入框/标签

tsx
import { Input, Label, YStack, XStack, Button } from 'tamagui'

<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>

  <XStack gap="$2" justifyContent="flex-end">
    <Button variant="outlined">Cancel</Button>
    <Button theme="blue">Submit</Button>
  </XStack>
</YStack>

tsx
import { Input, Label, YStack, XStack, Button } from 'tamagui'

<YStack gap="$4" padding="$4">
  <YStack gap="$2">
    <Label htmlFor="email">邮箱</Label>
    <Input
      id="email"
      placeholder="email@example.com"
      autoCapitalize="none"
      keyboardType="email-address"
    />
  </YStack>

  <XStack gap="$2" justifyContent="flex-end">
    <Button variant="outlined">取消</Button>
    <Button theme="blue">提交</Button>
  </XStack>
</YStack>

Anti-Patterns

反模式

❌ Hardcoded values instead of tokens

❌ 使用硬编码值而非令牌

tsx
// bad
<View padding={16} backgroundColor="#fff" />

// good - uses design tokens
<View padding="$4" backgroundColor="$background" />
tsx
// 错误示例
<View padding={16} backgroundColor="#fff" />

// 正确示例 - 使用设计令牌
<View padding="$4" backgroundColor="$background" />

❌ Missing
as const
on variants

❌ 变体对象缺少
as const

tsx
// bad - TypeScript can't infer variant types
variants: {
  size: { small: {...}, large: {...} }
}

// good
variants: {
  size: { small: {...}, large: {...} }
} as const
tsx
// 错误示例 - TypeScript无法推断变体类型
variants: {
  size: { small: {...}, large: {...} }
}

// 正确示例
variants: {
  size: { small: {...}, large: {...} }
} as const

❌ Platform detection in styled()

❌ 在styled()中检测平台

tsx
// bad - won't be extracted by compiler
const Box = styled(View, {
  padding: Platform.OS === 'web' ? 10 : 20,
})

// good - use platform modifiers
const Box = styled(View, {
  padding: 20,
  '$platform-web': { padding: 10 },
})
tsx
// 错误示例 - 编译器无法提取样式
const Box = styled(View, {
  padding: Platform.OS === 'web' ? 10 : 20,
})

// 正确示例 - 使用平台修饰符
const Box = styled(View, {
  padding: 20,
  '$platform-web': { padding: 10 },
})

❌ exitStyle without AnimatePresence

❌ 未使用AnimatePresence却添加exitStyle

tsx
// bad - exit animation won't work
{show && <View exitStyle={{ opacity: 0 }} />}

// good
<AnimatePresence>
  {show && <View key="box" exitStyle={{ opacity: 0 }} />}
</AnimatePresence>
tsx
// 错误示例 - 退出动画无法生效
{show && <View exitStyle={{ opacity: 0 }} />}

// 正确示例
<AnimatePresence>
  {show && <View key="box" exitStyle={{ opacity: 0 }} />}
</AnimatePresence>

❌ Dynamic values that prevent extraction

❌ 使用动态值导致无法提取样式

tsx
// bad - runtime variable prevents compiler extraction
const dynamicPadding = isPremium ? '$6' : '$4'
<View padding={dynamicPadding} />

// good - inline ternary is extractable
<View padding={isPremium ? '$6' : '$4'} />
tsx
// 错误示例 - 运行时变量导致编译器无法提取样式
const dynamicPadding = isPremium ? '$6' : '$4'
<View padding={dynamicPadding} />

// 正确示例 - 内联三元表达式可被提取
<View padding={isPremium ? '$6' : '$4'} />

❌ Wrong media query order

❌ 媒体查询顺序错误

tsx
// bad - base value overrides responsive
<View $gtMd={{ padding: '$8' }} padding="$4" />

// good - base first, then responsive overrides
<View padding="$4" $gtMd={{ padding: '$8' }} />
tsx
// 错误示例 - 基础值覆盖响应式值
<View $gtMd={{ padding: '$8' }} padding="$4" />

// 正确示例 - 先设置基础值,再添加响应式覆盖
<View padding="$4" $gtMd={{ padding: '$8' }} />

❌ Spring animations with CSS driver

❌ 在CSS驱动中使用弹簧动画

tsx
// bad - CSS driver doesn't support spring physics
import { createAnimations } from '@tamagui/animations-css'
const anims = createAnimations({
  bouncy: { type: 'spring', damping: 10 }  // won't work
})

// good for CSS driver - use easing strings
const anims = createAnimations({
  bouncy: 'cubic-bezier(0.68, -0.55, 0.265, 1.55) 300ms'
})

tsx
// 错误示例 - CSS驱动不支持弹簧物理
import { createAnimations } from '@tamagui/animations-css'
const anims = createAnimations({
  bouncy: { type: 'spring', damping: 10 }  // 无法生效
})

// 正确示例 - CSS驱动使用缓动字符串
const anims = createAnimations({
  bouncy: 'cubic-bezier(0.68, -0.55, 0.265, 1.55) 300ms'
})

Compiler Optimization

编译器优化

The Tamagui compiler extracts static styles to CSS at build time. For styles to be extracted:
  1. Use tokens -
    $4
    extracts,
    16
    may not
  2. Inline ternaries -
    padding={x ? '$4' : '$2'}
    extracts
  3. Avoid runtime variables - computed values don't extract
  4. Use variants - better than conditional props
Check if extraction is working:
  • Look for
    data-tamagui
    attributes in dev mode
  • Bundle size should be smaller with compiler enabled
  • Styles should appear as CSS classes, not inline

Tamagui编译器会在构建阶段将静态样式提取为CSS。要确保样式可被提取,请遵循以下规则:
  1. 使用令牌 -
    $4
    可被提取,
    16
    可能无法被提取
  2. 使用内联三元表达式 -
    padding={x ? '$4' : '$2'}
    可被提取
  3. 避免运行时变量 - 计算值无法被提取
  4. 使用变体 - 比条件属性更优
检查样式是否被提取:
  • 在开发模式下查看是否有
    data-tamagui
    属性
  • 启用编译器后包体积应更小
  • 样式应显示为CSS类,而非内联样式

TypeScript

TypeScript支持

tsx
import { GetProps, styled, View } from '@tamagui/core'

const MyComponent = styled(View, {
  variants: {
    size: { small: {}, large: {} }
  } as const,
})

// extract props type
type MyComponentProps = GetProps<typeof MyComponent>

// extend with custom props
interface ExtendedProps extends MyComponentProps {
  onCustomEvent?: () => void
}

tsx
import { GetProps, styled, View } from '@tamagui/core'

const MyComponent = styled(View, {
  variants: {
    size: { small: {}, large: {} }
  } as const,
})

// 提取属性类型
type MyComponentProps = GetProps<typeof MyComponent>

// 扩展自定义属性
interface ExtendedProps extends MyComponentProps {
  onCustomEvent?: () => void
}

Quick Reference

速查手册

PatternExample
Token
padding="$4"
Theme value
backgroundColor="$background"
Color scale
color="$color11"
(high contrast text)
Responsive
$gtSm={{ padding: '$6' }}
Variant
<Button size="large" variant="outlined" />
Animation
animation="quick" enterStyle={{ opacity: 0 }}
Theme switch
<Theme name="dark"><Theme name="blue">
Compound
<Card><Card.Title>
with
createStyledContext

模式示例
令牌
padding="$4"
主题值
backgroundColor="$background"
颜色体系
color="$color11"
(高对比度文本)
响应式
$gtSm={{ padding: '$6' }}
变体
<Button size="large" variant="outlined" />
动画
animation="quick" enterStyle={{ opacity: 0 }}
主题切换
<Theme name="dark"><Theme name="blue">
复合组件
<Card><Card.Title>
配合
createStyledContext

Resources

资源链接