关于 tamagui-v2
tamagui-v2 是面向跨平台 React 应用开发的 Tamagui 指南技能,涵盖 Web 与 React Native 的样式、组件和主题。内容还涉及 Bento 组件与 Takeout 全栈起始套件的使用模式。
主要功能
- 01统一的样式与设计令牌
- 02Web 和 React Native 组件
- 03编译器静态提取样式
- 04支持明暗模式的主题系统
- 05Bento 表单、表格与列表指南
- 06Takeout 路由、认证与数据指南
适用场景
- 01构建 Web 与原生共用的 React 界面
- 02使用令牌与主题统一应用样式
- 03为表单和数据表参考组件模式
- 04查阅 Takeout 全栈功能用法
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ramarivera/ tamagui-skills --skill tamagui-v2安装后在 Claude Code 中输入/tamagui-v2确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
__internal/__generation-guide.md__llms.txtreferences/animations.mdbento-forms.mdbento-lists.mdbento-tables.mdbreaking-changes-and-new-features.mdcompiler.mdcomponents.mdconfiguration.mdcore-styling.mdREADME.mdtakeout-auth.mdtakeout-database.mdtakeout-routing.mdtakeout-zero.mdtheming.md详细介绍
Tamagui v2 技能
适用于 Web 和原生平台的通用 React UI 框架(v1.144.0+)
此技能提供全面指导,帮助你使用 Tamagui 的样式系统、组件库、Bento 高级组件和 Takeout 入门套件构建跨平台 React 应用。
概述
Tamagui 是一个通用 UI 框架,让你只需编写一次代码,即可在 Web 和 React Native 上部署,并获得出色的性能。它具备以下特性:
- 统一的样式 API -
styled()函数,支持设计令牌、变体和响应式模式 - 优化编译器 - 在构建时将样式提取为原子 CSS,以实现最高性能
- 主题系统 - 12 阶色阶,自动支持浅色/深色模式
- 跨平台组件 - Button、Dialog、Sheet、Input、Select、Tabs 等
- 高级生态系统 - Bento(生产级表单、表格、列表)+ Takeout(全栈入门套件,包含路由、身份验证、数据库和实时同步)
版本: 1.144.0+
平台: Web(React)、iOS/Android(React Native)、Expo
许可证: 开源(MIT)+ Bento/Takeout(商业许可证)
何时使用此技能
遇到以下触发词时启用此技能:
核心框架
- "tamagui"、"通用 UI"、"react native web"、"跨平台 UI"
- "styled()"、"设计令牌"、"$token"、"主题令牌"
- "XStack"、"YStack"、"ZStack"、"Stack 组件"
- "useTheme"、"useMedia"、"响应式设计"
- "@tamagui/*" 导入、"@tamagui/core"
- "createStyledContext"、"withStaticProperties"
- "variant"、"variants"、"defaultVariants"
Bento 组件
- "bento"、"@tamagui/bento"、"bento 组件"
- "React Hook Form"、"react-hook-form"、"表单验证"
- "Zod"、"zod 验证"、"表单架构"
- "TanStack Table"、"@tanstack/react-table"、"数据表格"
- "虚拟列表"、"FlatList"、"瀑布流布局"
Takeout 入门套件
- "takeout"、"tamagui takeout"、"one.js"
- "基于文件的路由"、"one router"、"SSG"、"SSR"
- "Better Auth"、"身份验证"、"OAuth"
- "Drizzle ORM"、"数据库"、"PostgreSQL"
- "Zero Sync"、"实时同步"、"离线优先"
特定平台
- "动画驱动程序"、"CSS 动画"、"Reanimated"
- "编译器优化"、"静态提取"、"包体积"
- "仅限 Web"、"仅限原生平台"、"特定平台"
快速入门模式
1. 使用 styled() 设置基本样式
通过扩展现有组件来创建自定义组件:
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. 核心布局组件
Stack 是 Tamagui 布局的基础:
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. 使用设计令牌
设计令牌为应用中的值提供一致性:
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. 使用 12 阶色阶设置主题
Tamagui 使用语义化色阶系统(1-12)来保持主题一致:
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. 使用媒体查询实现响应式设计
使用媒体查询属性构建响应式布局:
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. 使用 enterStyle/exitStyle 添加动画
为任何组件添加流畅动画:
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. 使用 createStyledContext 构建复合组件
构建协调一致的组件系列:
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. 表单组件示例
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 protected]"
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 模式
import { Button, Dialog, XStack, YStack, H2, Paragraph } from 'tamagui'
export function DialogExample() {
return (
<Dialog>
<Dialog.Trigger asChild>
<Button>Open Dialog</Button>
</Dialog.Trigger>
<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"
>
<Dialog.Title>Confirm Action</Dialog.Title>
<Dialog.Description>
Are you sure you want to proceed?
</Dialog.Description>
<XStack alignSelf="flex-end" gap="$2">
<Dialog.Close displayWhenAdapted asChild>
<Button variant="outlined">Cancel</Button>
</Dialog.Close>
<Dialog.Close asChild>
<Button theme="blue">Confirm</Button>
</Dialog.Close>
</XStack>
</Dialog.Content>
</Dialog.Portal>
</Dialog>
)
}
10. Adapt 模式(小屏幕上使用移动端 Sheet)
import { Button, Dialog, Sheet, Adapt } from 'tamagui'
export function AdaptiveDialog() {
return (
<Dialog>
<Dialog.Trigger asChild>
<Button>Open</Button>
</Dialog.Trigger>
<Adapt when="sm" platform="touch">
<Sheet animation="medium" zIndex={200000} modal dismissOnSnapToBottom>
<Sheet.Frame padding="$4" gap="$4">
<Adapt.Contents />
</Sheet.Frame>
<Sheet.Overlay
animation="lazy"
enterStyle={{ opacity: 0 }}
exitStyle={{ opacity: 0 }}
/>
</Sheet>
</Adapt>
<Dialog.Portal>
<Dialog.Overlay />
<Dialog.Content>
{/* Content shows as Dialog on desktop, Sheet on mobile */}
<Dialog.Title>Adaptive UI</Dialog.Title>
<Dialog.Description>
This is a dialog on desktop, sheet on mobile
</Dialog.Description>
</Dialog.Content>
</Dialog.Portal>
</Dialog>
)
}
Bento 组件概览
来自 @tamagui/bento 的高级组件(生产环境使用需持有 Bento 许可证)。
表单系统
可组合的输入组件,集成 React Hook Form 和 Zod:
import { Input } from '@tamagui/bento/components/inputsParts'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
const schema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Password must be at least 8 characters'),
})
export function BentoForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema),
})
return (
<YStack gap="$4">
<Input size="$4">
<Input.Label htmlFor="email">Email</Input.Label>
<Input.Box>
<Input.Section>
<Mail size={16} />
</Input.Section>
<Input.Area
id="email"
placeholder="[email protected]"
{...register('email')}
/>
</Input.Box>
{errors.email && (
<Input.Info color="$red10">{errors.email.message}</Input.Info>
)}
</Input>
<Input size="$4">
<Input.Label htmlFor="password">Password</Input.Label>
<Input.Box>
<Input.Area
id="password"
secureTextEntry
placeholder="••••••••"
{...register('password')}
/>
<Input.Section>
<Input.Button>
<Eye size={16} />
</Input.Button>
</Input.Section>
</Input.Box>
{errors.password && (
<Input.Info color="$red10">{errors.password.message}</Input.Info>
)}
</Input>
</YStack>
)
}
主要特性:
- Input.Label - 通过
htmlFor绑定的无障碍标签 - Input.Box - 感知焦点的容器,带有
FocusContext - Input.Section - 用于图标/按钮的布局区域
- Input.Area - 实际的文本输入区域
- Input.Icon - 带主题样式的图标容器
- Input.Button - 输入框内的操作按钮
- Input.Info - 辅助/错误文本
使用 TanStack Table 构建表格
可用于生产环境的数据表格,支持排序、筛选和分页:
import { DataTable } from '@tamagui/bento/components/Table'
import { createColumnHelper } from '@tanstack/react-table'
type User = {
id: number
name: string
email: string
role: string
}
const columnHelper = createColumnHelper<User>()
const columns = [
columnHelper.accessor('name', {
header: 'Name',
cell: info => info.getValue(),
}),
columnHelper.accessor('email', {
header: 'Email',
cell: info => info.getValue(),
}),
columnHelper.accessor('role', {
header: 'Role',
cell: info => <Text textTransform="capitalize">{info.getValue()}</Text>,
}),
]
export function UsersTable({ users }: { users: User[] }) {
return (
<DataTable
data={users}
columns={columns}
enableSorting
enableFiltering
enablePagination
pageSize={10}
/>
)
}
虚拟化列表
使用 FlatList 模式高效渲染列表:
import { FlatList } from '@tamagui/bento/components/List'
import { YStack, Text } from 'tamagui'
export function VirtualizedList({ items }: { items: string[] }) {
return (
<FlatList
data={items}
keyExtractor={(item, index) => `${item}-${index}`}
renderItem={({ item }) => (
<YStack padding="$4" borderBottomWidth={1} borderColor="$borderColor">
<Text>{item}</Text>
</YStack>
)}
estimatedItemSize={60}
/>
)
}
其他模式:
- 瀑布流布局 - Pinterest 风格的网格
- 水平轮播 - 可滑动列表
- 动画列表 - 进入/退出动画
- 下拉刷新 - 适合移动设备的刷新方式
Takeout 技术栈概览
来自 Tamagui Takeout 的全栈入门套件(需要许可证)。
One.js 路由
基于文件的路由,支持 SSG/SSR/SPA 模式:
app/
_layout.tsx # Root layout
index.tsx # / (home page)
blog/
[slug].tsx # /blog/my-post (dynamic route)
(auth)/ # Route group (no URL nesting)
login.tsx # /login
signup.tsx # /signup
+not-found.tsx # Custom 404 page
类型安全的导航:
import { Link, useRouter, useParams } from 'one'
export function Navigation() {
const router = useRouter()
return (
<YStack gap="$2">
{/* Declarative navigation */}
<Link href="/">Home</Link>
<Link href="/blog/hello-world">Blog Post</Link>
{/* Programmatic navigation */}
<Button onPress={() => router.push('/about')}>
Go to About
</Button>
</YStack>
)
}
// Dynamic routes
export function BlogPost() {
const { slug } = useParams<{ slug: string }>()
return <Text>Post: {slug}</Text>
}
服务器端数据加载:
import { createLoader } from 'one'
export const loader = createLoader(async (context) => {
const post = await db.query.posts.findFirst({
where: eq(posts.slug, context.params.slug),
})
return { post }
})
export default function BlogPost() {
const { post } = useLoader(loader)
return <Text>{post.title}</Text>
}
Better Auth 集成
支持多种策略的完整身份验证系统:
import { signIn, signOut, useSession } from '~/lib/auth'
export function AuthExample() {
const session = useSession()
if (!session.user) {
return (
<YStack gap="$4">
{/* Email/password */}
<Button onPress={() => signIn.email({ email, password })}>
Sign In
</Button>
{/* OAuth */}
<Button onPress={() => signIn.social({ provider: 'github' })}>
Sign in with GitHub
</Button>
{/* Magic link */}
<Button onPress={() => signIn.magicLink({ email })}>
Send Magic Link
</Button>
</YStack>
)
}
return (
<YStack gap="$2">
<Text>Welcome, {session.user.name}</Text>
<Button onPress={() => signOut()}>Sign Out</Button>
</YStack>
)
}
功能:
- 电子邮件/密码身份验证
- OAuth(GitHub、Google 等)
- 魔法链接(电子邮件)
- OTP(电子邮件/电话)
- 会话管理
- 适用于原生应用的 JWT 令牌
Drizzle ORM(数据库)
使用 PostgreSQL 执行类型安全的数据库查询:
import { db } from '~/db'
import { posts, users } from '~/db/schema'
import { eq, desc } from 'drizzle-orm'
// Define schema
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
slug: text('slug').notNull().unique(),
content: text('content'),
authorId: integer('author_id').references(() => users.id),
createdAt: timestamp('created_at').defaultNow(),
})
// Query
const allPosts = await db.query.posts.findMany({
orderBy: [desc(posts.createdAt)],
with: {
author: true, // Join with users
},
})
// Insert
await db.insert(posts).values({
title: 'My Post',
slug: 'my-post',
content: 'Post content...',
authorId: 1,
})
// Update
await db.update(posts)
.set({ title: 'Updated Title' })
.where(eq(posts.id, 1))
// Delete
await db.delete(posts).where(eq(posts.id, 1))
Zero Sync(实时数据)
离线优先的实时同步:
import { useQuery, useMutation } from '@rocicorp/zero/react'
import { z } from '@rocicorp/zero'
// Define schema
const todoSchema = z.object({
id: z.string(),
text: z.string(),
completed: z.boolean(),
createdAt: z.number(),
})
export function TodoList() {
// Real-time query (updates automatically)
const [todos] = useQuery(q => q.todos.orderBy('createdAt', 'desc'))
// Optimistic mutation
const [addTodo] = useMutation(async (tx, text: string) => {
await tx.todos.insert({
id: crypto.randomUUID(),
text,
completed: false,
createdAt: Date.now(),
})
})
const [toggleTodo] = useMutation(async (tx, id: string) => {
const todo = await tx.todos.get(id)
if (todo) {
await tx.todos.update({
id,
completed: !todo.completed,
})
}
})
return (
<YStack gap="$2">
{todos.map(todo => (
<XStack key={todo.id} gap="$2">
<Checkbox
checked={todo.completed}
onCheckedChange={() => toggleTodo(todo.id)}
/>
<Text>{todo.text}</Text>
</XStack>
))}
</YStack>
)
}
功能:
- 在设备之间实时同步
- 离线优先架构
- 乐观变更(即时更新 UI)
- 自动解决冲突
- 使用 Zod 执行类型安全的查询
- 用于实现零延迟查询的客户端副本
参考指南
如需特定主题的详细文档,请参阅以下参考文件:
| 主题 | 参考文件 | 描述 |
|---|---|---|
| 核心样式 | core-styling.md | styled() API、变体系统、createStyledContext、组合模式、TypeScript 集成 |
| 组件 | components.md | Button、Dialog、Sheet、Input、Select、Tabs、Switch、Popover、Stack、Adapt 模式 |
| 主题 | theming.md | 12 级色阶系统、主题创建、动态主题切换、useTheme 钩子 |
| 动画 | animations.md | 动画驱动程序(CSS、React Native、Reanimated、Motion)、enterStyle/exitStyle、AnimatePresence |
| 配置 | configuration.md | createTamagui 函数、令牌、主题、字体、媒体查询、简写属性 |
| 编译器 | compiler.md | 静态提取、Babel 优化、原子 CSS 生成、平台特定设置 |
| Bento 表单 | bento-forms.md | React Hook Form 集成、Zod 验证、可组合输入系统、无障碍功能 |
| Bento 表格 | bento-tables.md | TanStack Table v8 集成、排序、分页、筛选、自适应布局 |
| Bento 列表 | bento-lists.md | FlatList 模式、虚拟化列表、瀑布流布局、性能优化 |
| Takeout 路由 | takeout-routing.md | One.js 基于文件的路由、SSG/SSR/SPA 模式、动态路由、服务器端数据加载 |
| Takeout 身份验证 | takeout-auth.md | Better Auth 集成、会话管理、OAuth、魔法链接、OTP |
| Takeout 数据库 | takeout-database.md | Drizzle ORM 设置、架构定义、类型安全的查询、迁移 |
| Takeout Zero | takeout-zero.md | 用于实时数据的 Zero Sync、离线优先架构、乐观变更 |
| 破坏性变更 | breaking-changes-and-new-features.md | 从旧版本迁移的指南、配置 v4/v5 的差异 |
常见问题
1. 令牌用法
❌ 错误:
<View padding={16} /> // Hard-coded value
<View padding="16px" /> // String with unit
✅ 正确:
<View padding="$4" /> // Token reference
<View padding={16} /> // Number is OK if intentional (not extracted by compiler)
2. 变体定义
❌ 错误:
// Missing `as const`
variants: {
size: {
small: { padding: '$2' },
large: { padding: '$4' },
},
}
✅ 正确:
// Must use `as const` for proper TypeScript inference
variants: {
size: {
small: { padding: '$2' },
large: { padding: '$4' },
},
} as const
3. 平台特定样式
❌ 错误:
// Using platform detection in styled()
const Component = styled(View, {
padding: Platform.OS === 'web' ? 10 : 20, // Won't extract
})
✅ 正确:
// Use platform prop modifiers
const Component = styled(View, {
padding: 20,
$platform-web: {
padding: 10,
},
})
4. 媒体查询属性顺序
❌ 错误:
<View
$gtMd={{ padding: '$8' }} // Applied first
padding="$4" // Overrides media query
/>
✅ 正确:
<View
padding="$4" // Base value
$gtMd={{ padding: '$8' }} // Overrides on medium+
/>
5. 动画驱动不匹配
❌ 错误:
// Using spring physics with CSS driver
import { createAnimations } from '@tamagui/animations-css'
const animations = createAnimations({
bouncy: {
type: 'spring', // CSS doesn't support springs!
damping: 10,
},
})
✅ 正确:
// CSS driver uses easing strings
import { createAnimations } from '@tamagui/animations-css'
const animations = createAnimations({
bouncy: 'cubic-bezier(0.68, -0.55, 0.265, 1.55) 500ms',
})
// Or use React Native driver for springs
import { createAnimations } from '@tamagui/animations-react-native'
const animations = createAnimations({
bouncy: {
type: 'spring',
damping: 10,
stiffness: 100,
},
})
6. 未设置静态属性的 Context
❌ 错误:
// Creating compound components without withStaticProperties
const Card = styled(View, { context: CardContext })
const CardTitle = styled(Text, { context: CardContext })
// Usage requires separate imports
import { Card } from './Card'
import { CardTitle } from './CardTitle'
<Card>
<CardTitle>Title</CardTitle>
</Card>
✅ 正确:
import { withStaticProperties } from '@tamagui/helpers'
const CardFrame = styled(View, { context: CardContext })
const CardTitle = styled(Text, { context: CardContext })
export const Card = withStaticProperties(CardFrame, {
Title: CardTitle,
})
// Single import
<Card>
<Card.Title>Title</Card.Title>
</Card>
7. 缺少 AnimatePresence
❌ 错误:
// Using exitStyle without AnimatePresence
{show && (
<View
animation="quick"
enterStyle={{ opacity: 0 }}
exitStyle={{ opacity: 0 }} // Won't animate on exit!
/>
)}
✅ 正确:
import { AnimatePresence } from 'tamagui'
<AnimatePresence>
{show && (
<View
key="animated-view" // Key is required!
animation="quick"
enterStyle={{ opacity: 0 }}
exitStyle={{ opacity: 0 }}
/>
)}
</AnimatePresence>
8. 内联样式与 styled()
❌ 错误(性能问题):
// Inline styles aren't extracted by compiler
{items.map(item => (
<View
key={item.id}
style={{
padding: 16,
backgroundColor: '#fff',
borderRadius: 8,
}}
/>
))}
✅ 正确:
// Use styled() for extractable styles
const ItemView = styled(View, {
padding: '$4',
backgroundColor: '$background',
borderRadius: '$2',
})
{items.map(item => (
<ItemView key={item.id} />
))}
9. Web 与 Native 的差异
请留意平台特定的行为:
- CSS 驱动器 - 仅限 Web,不支持弹簧物理效果
- Sheet 组件 - 在移动设备上配合触摸手势可提供更好的用户体验
- Adapt 模式 - 根据平台/屏幕尺寸自动切换组件
- 表单属性(type、action、method)- 仅限 Web
- 动画驱动器 - Reanimated 在 Web 上表现不佳,在 Native 上表现出色
10. 编译器优化
样式必须可静态分析,编译器才能提取:
❌ 不会提取:
const dynamicPadding = isPremium ? '$6' : '$4'
<View padding={dynamicPadding} /> // Runtime dynamic value
✅ 会提取:
<View padding={isPremium ? '$6' : '$4'} /> // Inline ternary is OK
// Or use variants
<View size={isPremium ? 'large' : 'small'} />
代码生成提示
生成 Tamagui 代码时,请遵循以下最佳实践:
1. 从配置开始
生成组件前,务必确保存在包含 tokens、主题和媒体查询的 createTamagui 配置。
2. 始终使用 Tokens
优先使用设计 tokens($4、$background、$color11),而非硬编码值,以便提取样式并支持主题化。
3. 利用变体提高灵活性
为组件创建尺寸、变体和状态变体,而不是分别定义多个组件:
// ✅ Good - One component with variants
const Button = styled(View, {
variants: {
variant: { primary: {...}, secondary: {...} },
size: { small: {...}, large: {...} },
} as const,
})
// ❌ Avoid - Multiple separate components
const PrimaryButton = styled(View, { ... })
const SecondaryButton = styled(View, { ... })
4. 使用 createStyledContext 进行组合
对于复合组件(Card、Input、Select),使用 createStyledContext 共享尺寸和样式:
const Context = createStyledContext({ size: '$4' })
const Parent = styled(View, { context: Context, variants: {...} })
const Child = styled(Text, { context: Context, variants: {...} })
export const Component = withStaticProperties(Parent, { Child })
5. 添加无障碍支持
务必添加无障碍属性:
<Input
id="email"
aria-label="Email address"
aria-describedby="email-error"
aria-invalid={!!error}
/>
<Label htmlFor="email">Email</Label>
{error && <Text id="email-error">{error}</Text>}
6. 默认支持响应式设计
添加响应式属性,提供更好的用户体验:
<YStack
padding="$4"
$gtSm={{ padding: '$6' }}
$gtMd={{ padding: '$8' }}
flexDirection="column"
$gtLg={{ flexDirection: 'row' }}
/>
7. 动画模式
使用 enterStyle/exitStyle 实现进入/退出动画,并用 AnimatePresence 包裹:
<AnimatePresence>
{show && (
<View
key="unique-key"
animation="quick"
enterStyle={{ opacity: 0, y: -10 }}
exitStyle={{ opacity: 0, y: 10 }}
opacity={1}
y={0}
/>
)}
</AnimatePresence>
8. 平台适配
使用 Adapt 模式构建适合移动设备的界面:
<Dialog>
<Adapt when="sm" platform="touch">
<Sheet>
<Adapt.Contents />
</Sheet>
</Adapt>
<Dialog.Portal>
<Dialog.Content>...</Dialog.Content>
</Dialog.Portal>
</Dialog>
9. TypeScript 类型
务必导出可复用组件的属性类型:
import { GetProps } from '@tamagui/core'
export const MyComponent = styled(View, { ... })
export type MyComponentProps = GetProps<typeof MyComponent>
10. 测试注意事项
生成测试代码时:
- 使用
testID属性标识组件(跨平台) - 使用
TamaguiProvider模拟主题 Context - 使用媒体查询模拟来测试响应式行为
- 使用
AnimatePresence的退出回调验证动画
版本与生态说明
当前版本: 1.144.0+
依赖项:
- React 19+(或 React 18.2+)
- React Native(用于 Native 目标平台)
- Expo SDK(可选,用于托管工作流)
生态系统包:
| 包 | 描述 |
|---|---|
tamagui | 包含核心功能和组件的元包 |
@tamagui/core | 核心样式引擎 |
@tamagui/animations-css | CSS 动画驱动器 |
@tamagui/animations-react-native | React Native 动画驱动器 |
@tamagui/animations-reanimated | Reanimated v3 驱动器 |
@tamagui/animations-moti | Moti/Motion 驱动器 |
@tamagui/themes | 主题创建辅助工具 |
@tamagui/colors | Radix UI 色阶 |
@tamagui/bento | 高级组件(需要许可证) |
| Tamagui Takeout | 全栈起步模板(需要许可证) |
从 v1.x 迁移:
- 阅读
breaking-changes-and-new-features.md - 更新为 v4 或 v5 配置格式
- 将媒体查询调整为 v4 命名方式
- 替换已弃用的模式
快速链接
- 官方文档: https://tamagui.dev
- GitHub: https://github.com/tamagui/tamagui
- Bento 组件: https://tamagui.dev/bento
- Takeout 起步模板: https://tamagui.dev/takeout
- 社区: https://discord.gg/4qh6tdcVDa
- Twitter: https://twitter.com/tamagui_js
其他资源
如需查看全面的示例和模式,请浏览:
- Tamagui Kitchen Sink: https://tamagui.dev/kitchen-sink
- Bento 组件库: https://tamagui.dev/bento/showcase
- Takeout 演示: https://tamagui.dev/takeout/demo
- GitHub 讨论区: https://github.com/tamagui/tamagui/discussions
最后更新: 2026 年 1 月
技能版本: 2.0
生成自: Tamagui 官方文档(v1.144.0+)