tamagui

star 1

Universal UI kit for React Native and web — performant, themeable, typed

UltronCore By UltronCore schedule Updated 5/24/2026

name: tamagui version: 1.0.0 description: Universal UI kit for React Native and web — performant, themeable, typed tools: [Read, Edit, Write, Bash] tags: [mobile, web, react-native, tamagui, ui-library, universal, typescript, styled] author: claude-skill-vault created: 2026-05-24

Tamagui — Universal UI Kit

Overview

Tamagui is a universal UI library and style system that works across React Native and the web from a single codebase. It compiles styles at build time to avoid runtime overhead, ships with a complete component library, and supports themes, tokens, and animations natively. On the web it generates atomic CSS; on native it uses StyleSheet with optimized props.

When to Use

  • Universal apps targeting React Native + web with shared UI components
  • Design systems that must look and behave the same on iOS, Android, and web
  • Replacing styled-components/StyleSheet with a typed, design-token-aware system
  • Expo or Next.js projects that want a polished out-of-the-box component library
  • Apps needing dark mode, theming, and responsive design across platforms

Installation / Setup

# With Expo (recommended)
npx create-expo-app my-app -t with-tamagui

# Manual (Expo project)
npx expo install tamagui @tamagui/config @tamagui/babel-plugin

# Manual (Next.js)
npm install tamagui @tamagui/config @tamagui/next-plugin
// tamagui.config.ts
import { createTamagui } from 'tamagui';
import { config } from '@tamagui/config/v3';

export const tamaguiConfig = createTamagui(config);
export type Conf = typeof tamaguiConfig;
declare module 'tamagui' {
  interface TamaguiCustomConfig extends Conf {}
}
export default tamaguiConfig;
// App.tsx — wrap with TamaguiProvider
import { TamaguiProvider } from 'tamagui';
import tamaguiConfig from './tamagui.config';

export default function App() {
  return (
    <TamaguiProvider config={tamaguiConfig} defaultTheme="light">
      <AppContent />
    </TamaguiProvider>
  );
}

Key Patterns

Stack, XStack, YStack (Layout Primitives)

import { XStack, YStack, Text, Button } from 'tamagui';

function Card({ title, subtitle }: { title: string; subtitle: string }) {
  return (
    <YStack
      padding="$4"          // uses $4 spacing token
      gap="$2"
      backgroundColor="$background"
      borderRadius="$4"
      borderWidth={1}
      borderColor="$borderColor"
    >
      <Text fontSize="$6" fontWeight="700" color="$color">
        {title}
      </Text>
      <Text fontSize="$3" color="$colorSubtle">
        {subtitle}
      </Text>
      <XStack gap="$2" justifyContent="flex-end">
        <Button size="$3" variant="outlined">Cancel</Button>
        <Button size="$3" theme="active">Confirm</Button>
      </XStack>
    </YStack>
  );
}

Theming

import { Theme, XStack, Button, Text } from 'tamagui';

// Apply a sub-theme to a subtree
function DangerZone() {
  return (
    <Theme name="red">
      <XStack backgroundColor="$background" padding="$4" gap="$3">
        <Text color="$color">This is danger zone</Text>
        <Button>Delete Account</Button>
      </XStack>
    </Theme>
  );
}

// Dark/light toggle
import { useColorScheme } from 'react-native';
function ThemedApp() {
  const scheme = useColorScheme();
  return (
    <TamaguiProvider config={tamaguiConfig} defaultTheme={scheme ?? 'light'}>
      <App />
    </TamaguiProvider>
  );
}

Responsive Design (Media Queries)

import { YStack, Text } from 'tamagui';

function ResponsiveLayout() {
  return (
    <YStack
      width="100%"
      $gtSm={{ flexDirection: 'row', maxWidth: 800 }}  // > sm breakpoint
      $sm={{ flexDirection: 'column' }}                  // sm breakpoint
      padding="$4"
    >
      <YStack flex={1} $gtSm={{ flex: 0.4 }}>
        <Text>Sidebar</Text>
      </YStack>
      <YStack flex={1} $gtSm={{ flex: 0.6 }}>
        <Text>Content</Text>
      </YStack>
    </YStack>
  );
}

Styled Components (Custom Design System)

import { styled, Text, YStack } from 'tamagui';

// Extend existing Tamagui components
const Heading = styled(Text, {
  fontSize: '$8',
  fontWeight: '700',
  color: '$color',

  variants: {
    size: {
      sm: { fontSize: '$6' },
      md: { fontSize: '$8' },
      lg: { fontSize: '$10' },
    },
    muted: {
      true: { color: '$colorSubtle', opacity: 0.7 },
    },
  } as const,
});

const Card = styled(YStack, {
  backgroundColor: '$background',
  borderRadius: '$4',
  padding: '$4',
  borderWidth: 1,
  borderColor: '$borderColor',

  pressStyle: { opacity: 0.8 },
  hoverStyle: { borderColor: '$colorFocus' },
});

// Usage
<Heading size="lg">Hello World</Heading>
<Card onPress={handlePress}>
  <Heading muted>Subtitle</Heading>
</Card>

Animations

import { AnimatePresence, YStack } from 'tamagui';
import { useState } from 'react';

function FadeToggle() {
  const [show, setShow] = useState(true);

  return (
    <>
      <Button onPress={() => setShow(v => !v)}>Toggle</Button>
      <AnimatePresence>
        {show && (
          <YStack
            key="content"
            animation="quick"   // built-in animation preset
            enterStyle={{ opacity: 0, y: -10 }}
            exitStyle={{ opacity: 0, y: 10 }}
          >
            <Text>Animated content</Text>
          </YStack>
        )}
      </AnimatePresence>
    </>
  );
}

Sheet (Bottom Sheet)

import { Sheet, Button, YStack, Text } from 'tamagui';
import { useState } from 'react';

function BottomSheetExample() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Button onPress={() => setOpen(true)}>Open Sheet</Button>
      <Sheet
        open={open}
        onOpenChange={setOpen}
        snapPoints={[85, 50, 25]}
        dismissOnSnapToBottom
        animation="medium"
      >
        <Sheet.Overlay />
        <Sheet.Handle />
        <Sheet.Frame padding="$4">
          <YStack gap="$3">
            <Text fontSize="$6" fontWeight="700">Sheet Title</Text>
            <Text>Sheet content goes here.</Text>
            <Button onPress={() => setOpen(false)}>Close</Button>
          </YStack>
        </Sheet.Frame>
      </Sheet>
    </>
  );
}

Common Pitfalls

  • Babel plugin required: without @tamagui/babel-plugin, styles don't compile and performance degrades significantly — always configure it
  • $token vs raw values: always prefer $4 (token) over 16 (raw) — tokens respond to theme changes; raw values don't
  • pressStyle/hoverStyle not on arbitrary components: pseudo-styles only work on Tamagui's native interactable components or components that pass through interaction props
  • Web-only media queries: $gtSm, $lg, etc. work on web via CSS; on native they're evaluated at runtime — test both
  • SSR with Next.js requires @tamagui/next-plugin: without it, server-rendered HTML has no styles and there's a flash of unstyled content

Related Skills

  • nativewind — Tailwind alternative for React Native without universal ambition
  • expo-router — routing that pairs naturally with Tamagui
  • react-native-reanimated — advanced animations beyond Tamagui's built-ins
  • react-native-best-practices — React Native fundamentals

GitNexus Index

domain: mobile,frontend/web
tier: library
runtime: ios,android,browser
language: tsx,ts
framework: react-native,expo,next
purpose: ui-components
Install via CLI
npx skills add https://github.com/UltronCore/claude-skill-vault --skill tamagui
Repository Details
star Stars 1
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator