mcpField
T
tamagui-v2

tamagui-v2

github.com/ramariveraデザイン・UI0114 回インストール40 週間前に更新
リポジトリ

tamagui-v2 について

tamagui-v2 は、Tamagui を使ったクロスプラットフォーム React アプリ開発のガイドスキルです。Web と React Native のスタイリング、コンポーネント、テーマに加え、Bento と Takeout の利用パターンも扱います。

主な機能

  1. 01統一されたスタイリングとデザイントークン
  2. 02Web・React Native 向けコンポーネント
  3. 03コンパイラーによるスタイルの静的抽出
  4. 04ライト・ダークモード対応テーマ
  5. 05Bento のフォーム・テーブル・リスト
  6. 06Takeout のルーティング・認証・データガイド

活用シーン

  1. 01Web とネイティブ向け React UI の構築
  2. 02トークンとテーマによる一貫したスタイル設定
  3. 03フォームやテーブルの実装パターンの参照
  4. 04Takeout のフルスタック機能の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/ramarivera/tamagui-skills --skill tamagui-v2

インストール後、Claude Code で/tamagui-v2を入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

__internal/
__generation-guide.md
__llms.txt
references/
animations.md
bento-forms.md
bento-lists.md
bento-tables.md
breaking-changes-and-new-features.md
compiler.md
components.md
configuration.md
core-styling.md
README.md
takeout-auth.md
takeout-database.md
takeout-routing.md
takeout-zero.md
theming.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.mdstyled() API、バリアントシステム、createStyledContext、合成パターン、TypeScript 統合
コンポーネントcomponents.mdButton、Dialog、Sheet、Input、Select、Tabs、Switch、Popover、Stacks、Adapt パターン
テーマ設定theming.md12 段階のカラー スケールシステム、テーマの作成、動的なテーマ切り替え、useTheme フック
アニメーションanimations.mdアニメーションドライバー(CSS、React Native、Reanimated、Motion)、enterStyle/exitStyle、AnimatePresence
設定configuration.mdcreateTamagui 関数、トークン、テーマ、フォント、メディアクエリ、短縮形
コンパイラーcompiler.md静的抽出、Babel 最適化、アトミック CSS 生成、プラットフォーム固有のセットアップ
Bento フォームbento-forms.mdReact Hook Form 統合、Zod バリデーション、合成可能な入力システム、アクセシビリティ
Bento テーブルbento-tables.mdTanStack Table v8 統合、並べ替え、ページネーション、フィルタリング、レスポンシブレイアウト
Bento リストbento-lists.mdFlatList パターン、仮想化リスト、メイソンリーレイアウト、パフォーマンス最適化
Takeout ルーティングtakeout-routing.mdOne.js のファイルベースルーティング、SSG/SSR/SPA モード、動的ルート、サーバーサイドのデータ読み込み
Takeout 認証takeout-auth.mdBetter Auth 統合、セッション管理、OAuth、マジックリンク、OTP
Takeout データベースtakeout-database.mdDrizzle ORM のセットアップ、スキーマ定義、型安全なクエリ、マイグレーション
Takeout Zerotakeout-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. テストに関する考慮事項

テストコードを生成する際は、以下の点に注意してください。

  • コンポーネントを識別するために testID prop を使用します(クロスプラットフォーム対応)
  • TamaguiProvider でテーマのコンテキストをモックします
  • メディアクエリーのモックを使用してレスポンシブ動作をテストします
  • AnimatePresence の終了コールバックでアニメーションを検証します

バージョンとエコシステムに関する注記

現在のバージョン: 1.144.0+

依存関係:

  • React 19+(または React 18.2+)
  • React Native(ネイティブターゲット用)
  • Expo SDK(任意。マネージドワークフロー用)

エコシステムパッケージ:

PackageDescription
tamaguiコアとコンポーネントを含むメタパッケージ
@tamagui/coreコアスタイリングエンジン
@tamagui/animations-cssCSS アニメーションドライバー
@tamagui/animations-react-nativeReact Native アニメーションドライバー
@tamagui/animations-reanimatedReanimated v3 ドライバー
@tamagui/animations-motiMoti/Motion ドライバー
@tamagui/themesテーマ作成ヘルパー
@tamagui/colorsRadix UI のカラースケール
@tamagui/bentoプレミアムコンポーネント(ライセンスが必要)
Tamagui Takeoutフルスタックのスターター(ライセンスが必要)

v1.x からの移行:

  • breaking-changes-and-new-features.md をお読みください
  • v4 または v5 の設定形式に更新します
  • メディアクエリーを v4 の命名規則に合わせます
  • 非推奨のパターンを置き換えます

クイックリンク


その他のリソース

包括的な例やパターンについては、以下をご覧ください。


最終更新日: 2026年1月
スキルバージョン: 2.0
生成元: 公式 Tamagui ドキュメント(v1.144.0+)

コメント