npx skills add https://github.com/tamagui/tamagui --skill tamagui适用于 Web 和原生应用的通用 React UI 框架,附带优化编译器。
在编写 Tamagui 代码之前,请获取项目的实际配置:
npx tamagui generate-prompt
这将输出 tamagui-prompt.md 文件,其中包含项目的具体信息:
请始终参考此文件获取令牌/主题/媒体查询名称,而不是猜测或使用默认值。
通过扩展现有组件来创建新组件:
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, // 类型推断所必需
defaultVariants: {
size: 'small',
},
})
// 用法
<Card size="large" elevated />
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
关键规则:
as const$ 前缀:$4、$background、$color11import { 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>
主题可以嵌套并分层组合:
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:文本色(从低对比度到高对比度)使用媒体查询属性(请检查你的 tamagui-prompt.md 文件以获取实际的断点名称):
<YStack
padding="$4"
$gtSm={{ padding: '$6' }} // 请检查你的配置以获取实际名称
$gtMd={{ padding: '$8' }}
flexDirection="column"
$gtLg={{ flexDirection: 'row' }}
/>
// 或者使用钩子
const media = useMedia()
if (media.gtMd) {
// 为中等及以上屏幕渲染
}
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 驱动使用缓动字符串,其他驱动支持弹簧物理效果。
使用 createStyledContext 来创建共享状态的组件:
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>
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>
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>
// 错误
<View padding={16} backgroundColor="#fff" />
// 正确 - 使用设计令牌
<View padding="$4" backgroundColor="$background" />
as const// 错误 - TypeScript 无法推断变体类型
variants: {
size: { small: {...}, large: {...} }
}
// 正确
variants: {
size: { small: {...}, large: {...} }
} as const
// 错误 - 编译器无法提取
const Box = styled(View, {
padding: Platform.OS === 'web' ? 10 : 20,
})
// 正确 - 使用平台修饰符
const Box = styled(View, {
padding: 20,
'$platform-web': { padding: 10 },
})
// 错误 - 退出动画不会生效
{show && <View exitStyle={{ opacity: 0 }} />}
// 正确
<AnimatePresence>
{show && <View key="box" exitStyle={{ opacity: 0 }} />}
</AnimatePresence>
// 错误 - 运行时变量阻止编译器提取
const dynamicPadding = isPremium ? '$6' : '$4'
<View padding={dynamicPadding} />
// 正确 - 内联三元表达式可被提取
<View padding={isPremium ? '$6' : '$4'} />
// 错误 - 基础值覆盖了响应式值
<View $gtMd={{ padding: '$8' }} padding="$4" />
// 正确 - 基础值在前,然后响应式覆盖
<View padding="$4" $gtMd={{ padding: '$8' }} />
// 错误 - 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'
})
Tamagui 编译器在构建时将静态样式提取为 CSS。要使样式被提取:
$4 可被提取,16 可能不行padding={x ? '$4' : '$2'} 可被提取检查提取是否正常工作:
data-tamagui 属性import { GetProps, styled, View } from '@tamagui/core'
const MyComponent = styled(View, {
variants: {
size: { small: {}, large: {} }
} as const,
})
// 提取 props 类型
type MyComponentProps = GetProps<typeof MyComponent>
// 使用自定义 props 扩展
interface ExtendedProps extends MyComponentProps {
onCustomEvent?: () => void
}
| 模式 | 示例 |
|---|---|
| 令牌 | 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"> |
| 复合组件 | 使用 createStyledContext 的 <Card><Card.Title> |
每周安装量
185
仓库
GitHub 星标
13.8K
首次出现
2026年2月3日
安全审计
已安装于
codex178
opencode178
gemini-cli177
github-copilot176
amp172
kimi-cli171
Universal React UI framework for web and native with an optimizing compiler.
Before writing Tamagui code , get the project's actual configuration:
npx tamagui generate-prompt
This outputs tamagui-prompt.md with the project's specific:
Always reference this file for token/theme/media query names rather than guessing or using defaults.
Create components by extending existing ones:
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:
as const on variants objects$ prefix: $4, $background, $color11import { 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>
Themes nest and combine hierarchically:
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)Use media query props (check your tamagui-prompt.md for actual breakpoint names):
<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
}
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 physicsCSS driver uses easing strings, others support spring physics.
Use createStyledContext for components that share state:
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>
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>
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>
// bad
<View padding={16} backgroundColor="#fff" />
// good - uses design tokens
<View padding="$4" backgroundColor="$background" />
as const on variants// bad - TypeScript can't infer variant types
variants: {
size: { small: {...}, large: {...} }
}
// good
variants: {
size: { small: {...}, large: {...} }
} as const
// 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 },
})
// bad - exit animation won't work
{show && <View exitStyle={{ opacity: 0 }} />}
// good
<AnimatePresence>
{show && <View key="box" exitStyle={{ opacity: 0 }} />}
</AnimatePresence>
// bad - runtime variable prevents compiler extraction
const dynamicPadding = isPremium ? '$6' : '$4'
<View padding={dynamicPadding} />
// good - inline ternary is extractable
<View padding={isPremium ? '$6' : '$4'} />
// bad - base value overrides responsive
<View $gtMd={{ padding: '$8' }} padding="$4" />
// good - base first, then responsive overrides
<View padding="$4" $gtMd={{ padding: '$8' }} />
// 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'
})
The Tamagui compiler extracts static styles to CSS at build time. For styles to be extracted:
$4 extracts, 16 may notpadding={x ? '$4' : '$2'} extractsCheck if extraction is working:
data-tamagui attributes in dev modeimport { 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
}
| Pattern | Example |
|---|---|
| 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 }} |
Weekly Installs
185
Repository
GitHub Stars
13.8K
First Seen
Feb 3, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex178
opencode178
gemini-cli177
github-copilot176
amp172
kimi-cli171
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
111,800 周安装
| Theme switch | <Theme name="dark"><Theme name="blue"> |
| Compound | <Card><Card.Title> with createStyledContext |