tamagui-v2 について
tamagui-v2 は、Tamagui を使ったクロスプラットフォーム React アプリ開発のガイドスキルです。Web と React Native のスタイリング、コンポーネント、テーマに加え、Bento と Takeout の利用パターンも扱います。
主な機能
- 01統一されたスタイリングとデザイントークン
- 02Web・React Native 向けコンポーネント
- 03コンパイラーによるスタイルの静的抽出
- 04ライト・ダークモード対応テーマ
- 05Bento のフォーム・テーブル・リスト
- 06Takeout のルーティング・認証・データガイド
活用シーン
- 01Web とネイティブ向け React UI の構築
- 02トークンとテーマによる一貫したスタイル設定
- 03フォームやテーブルの実装パターンの参照
- 04Takeout のフルスタック機能の確認
インストール
コマンドをコピーしてターミナルで実行します。
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 は、一度コードを書くだけで、最適なパフォーマンスで Web と React Native の両方にデプロイできるユニバーサル UI フレームワークです。次の機能を備えています。
- 統一されたスタイリング 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」、「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 コンポーネント
- 「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 スターター
- 「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」
プラットフォーム固有
- 「animation driver」、「CSS animations」、「Reanimated」
- 「compiler optimization」、「static extraction」、「bundle size」
- 「web-only」、「native-only」、「platform-specific」
クイックスタートパターン
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. ダイアログパターン
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 パターン(小さい画面ではモバイルシートを表示)
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 を使った型安全なクエリ
- 0 レイテンシのクエリを実現するクライアントサイドレプリカ
リファレンスガイド
特定のトピックに関する詳細なドキュメントは、次のリファレンスファイルを参照してください。
| トピック | リファレンスファイル | 説明 |
|---|---|---|
| コアスタイリング | core-styling.md | styled() API、バリアントシステム、createStyledContext、合成パターン、TypeScript 統合 |
| コンポーネント | components.md | Button、Dialog、Sheet、Input、Select、Tabs、Switch、Popover、Stacks、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 | 旧バージョンからの移行ガイド、config 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 コンポーネント - タッチジェスチャーにより、モバイルで優れた UX を実現します
- 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. 設定から始める
コンポーネントを生成する前に、トークン、テーマ、メディアクエリーを含む createTamagui 設定が存在することを必ず確認してください。
2. トークンを一貫して使用する
抽出可能でテーマに対応したスタイルにするため、ハードコードされた値ではなくデザイントークン($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. アクセシビリティを含める
アクセシビリティ用の props を必ず追加してください。
<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. デフォルトでレスポンシブにする
UX を向上させるため、レスポンシブ props を含めてください。
<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. プラットフォームへの適応
モバイルに適した UI には Adapt パターンを使用してください。
<Dialog>
<Adapt when="sm" platform="touch">
<Sheet>
<Adapt.Contents />
</Sheet>
</Adapt>
<Dialog.Portal>
<Dialog.Content>...</Dialog.Content>
</Dialog.Portal>
</Dialog>
9. TypeScript の型
再利用可能なコンポーネントでは、props の型を必ずエクスポートしてください。
import { GetProps } from '@tamagui/core'
export const MyComponent = styled(View, { ... })
export type MyComponentProps = GetProps<typeof MyComponent>
10. テストに関する考慮事項
テストコードを生成する際は、以下の点に注意してください。
- コンポーネントを識別するために
testIDprop を使用します(クロスプラットフォーム対応) TamaguiProviderでテーマのコンテキストをモックします- メディアクエリーのモックを使用してレスポンシブ動作をテストします
AnimatePresenceの終了コールバックでアニメーションを検証します
バージョンとエコシステムに関する注記
現在のバージョン: 1.144.0+
依存関係:
- React 19+(または React 18.2+)
- React Native(ネイティブターゲット用)
- Expo SDK(任意。マネージドワークフロー用)
エコシステムパッケージ:
| Package | Description |
|---|---|
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 Component Library: https://tamagui.dev/bento/showcase
- Takeout Demo: https://tamagui.dev/takeout/demo
- GitHub Discussions: https://github.com/tamagui/tamagui/discussions
最終更新日: 2026年1月
スキルバージョン: 2.0
生成元: 公式 Tamagui ドキュメント(v1.144.0+)