Fix: Tamagui Not Working — Styles Not Applying, Compiler Errors, or Web/Native Mismatch
Quick Answer
How to fix Tamagui UI kit issues — setup with Expo, theme tokens, styled components, animations, responsive props, media queries, and cross-platform rendering.
The Problem
Tamagui components render without styles:
import { Button, Text, YStack } from 'tamagui';
function App() {
return (
<YStack padding="$4" backgroundColor="$background">
<Text fontSize="$6" color="$color">Hello</Text>
<Button>Click me</Button>
</YStack>
);
}
// Components render but with no visual stylingOr the compiler throws during build:
Error: Tamagui: Expected theme to be definedOr styles work on web but not on native (or vice versa):
Button looks correct in web browser but has wrong colors on iOSWhy This Happens
Tamagui is a universal UI kit that works on React Native and web. It uses a compile-time optimizer and token-based theming:
TamaguiProviderwith a config is required — all Tamagui components read theme tokens and settings from a provider. Without it,$4,$background,$colorresolve to nothing.- The tamagui config must define tokens and themes — tokens are design values (spacing, colors, fonts). Themes map semantic names to token values. Missing definitions cause “Expected theme” errors.
- The compiler plugin optimizes styles at build time — Tamagui extracts static styles into CSS (web) or StyleSheet (native) during compilation. Without the plugin, styles work but aren’t optimized, and some features may break.
- Web and native have different rendering paths — on web, Tamagui outputs CSS. On native, it outputs StyleSheet objects. Platform-specific bugs usually stem from features that exist on one platform but not the other (e.g., CSS hover states don’t exist on native).
Fix 1: Setup with Expo
npx create-tamagui@latest --template expo-router
# Or add to existing Expo project:
npm install tamagui @tamagui/config
npx expo install react-native-reanimated// tamagui.config.ts
import { createTamagui } from 'tamagui';
import { config } from '@tamagui/config/v3';
// Use the default config (includes tokens, themes, fonts, etc.)
export const tamaguiConfig = createTamagui(config);
// Or customize:
export const tamaguiConfig = createTamagui({
...config,
tokens: {
...config.tokens,
color: {
...config.tokens.color,
primary: '#3b82f6',
secondary: '#64748b',
},
},
themes: {
...config.themes,
// Custom themes extend or override defaults
},
});
// Type declaration
export type AppConfig = typeof tamaguiConfig;
declare module 'tamagui' {
interface TamaguiCustomConfig extends AppConfig {}
}// app/_layout.tsx — wrap with TamaguiProvider
import { TamaguiProvider } from 'tamagui';
import { tamaguiConfig } from '../tamagui.config';
export default function RootLayout() {
return (
<TamaguiProvider config={tamaguiConfig} defaultTheme="light">
<Stack />
</TamaguiProvider>
);
}// metro.config.js — add Tamagui transform
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
// Add tamagui file extensions
config.resolver.sourceExts.push('mjs');
module.exports = config;Fix 2: Core Components
import {
YStack, XStack, Text, Button, Input, Card, H1, H2, Paragraph,
Separator, Avatar, Sheet, Switch, Label, Checkbox, RadioGroup,
ScrollView, Image, Spinner,
} from 'tamagui';
function ProfileScreen() {
return (
<ScrollView flex={1} backgroundColor="$background">
{/* Vertical stack */}
<YStack padding="$4" gap="$4">
{/* Header */}
<XStack alignItems="center" gap="$3">
<Avatar circular size="$6">
<Avatar.Image src="https://example.com/avatar.jpg" />
<Avatar.Fallback backgroundColor="$blue5" />
</Avatar>
<YStack>
<H2>Alice Johnson</H2>
<Paragraph theme="alt2">[email protected]</Paragraph>
</YStack>
</XStack>
<Separator />
{/* Card */}
<Card elevate bordered padding="$4">
<Card.Header>
<H2>Settings</H2>
</Card.Header>
{/* Toggle */}
<XStack alignItems="center" justifyContent="space-between" paddingVertical="$2">
<Label htmlFor="notifications">Notifications</Label>
<Switch id="notifications" size="$3">
<Switch.Thumb animation="bouncy" />
</Switch>
</XStack>
<XStack alignItems="center" justifyContent="space-between" paddingVertical="$2">
<Label htmlFor="darkMode">Dark Mode</Label>
<Switch id="darkMode" size="$3">
<Switch.Thumb animation="bouncy" />
</Switch>
</XStack>
</Card>
{/* Input */}
<YStack gap="$2">
<Label htmlFor="bio">Bio</Label>
<Input id="bio" placeholder="Tell us about yourself..." />
</YStack>
{/* Buttons */}
<XStack gap="$3">
<Button flex={1} theme="active">Save</Button>
<Button flex={1} variant="outlined">Cancel</Button>
</XStack>
</YStack>
</ScrollView>
);
}Fix 3: Responsive and Adaptive Props
import { YStack, Text, XStack, useMedia } from 'tamagui';
// Media query props — change based on screen size
function ResponsiveLayout() {
return (
<YStack
padding="$3"
$gtSm={{ padding: '$4' }} // > 660px
$gtMd={{ padding: '$6' }} // > 800px
$gtLg={{ padding: '$8' }} // > 1020px
>
<XStack
flexDirection="column"
$gtSm={{ flexDirection: 'row' }}
gap="$4"
>
<YStack flex={1}>
<Text
fontSize="$5"
$gtMd={{ fontSize: '$8' }}
>
Responsive heading
</Text>
</YStack>
<YStack
width="100%"
$gtSm={{ width: '50%' }}
$gtMd={{ width: '33%' }}
>
<Text>Sidebar content</Text>
</YStack>
</XStack>
</YStack>
);
}
// useMedia hook for conditional rendering
function AdaptiveContent() {
const media = useMedia();
return (
<YStack>
{media.gtMd ? (
<DesktopLayout />
) : (
<MobileLayout />
)}
<Text>{media.sm ? 'Small screen' : 'Large screen'}</Text>
</YStack>
);
}Fix 4: Themes and Dark Mode
import { YStack, Text, Button, useTheme, Theme } from 'tamagui';
import { useColorScheme } from 'react-native';
// Theme switching
function ThemedApp() {
const colorScheme = useColorScheme();
return (
<TamaguiProvider config={tamaguiConfig} defaultTheme={colorScheme || 'light'}>
<AppContent />
</TamaguiProvider>
);
}
// Nested theme — override for a section
function DarkSection() {
return (
<Theme name="dark">
<YStack backgroundColor="$background" padding="$4">
<Text color="$color">This section is always dark</Text>
</YStack>
</Theme>
);
}
// Access theme values programmatically
function ThemedComponent() {
const theme = useTheme();
return (
<YStack>
<Text>Primary color: {theme.color.val}</Text>
<Text>Background: {theme.background.val}</Text>
</YStack>
);
}
// Sub-themes (color variations)
<Theme name="blue">
<Button>Blue themed button</Button>
</Theme>
<Theme name="red">
<Button theme="active">Red active button</Button>
</Theme>Fix 5: Animations
import { YStack, Text, Button, AnimatePresence } from 'tamagui';
import { useState } from 'react';
function AnimatedCard() {
const [show, setShow] = useState(true);
return (
<YStack>
<Button onPress={() => setShow(!show)}>Toggle</Button>
<AnimatePresence>
{show && (
<YStack
animation="bouncy"
enterStyle={{ opacity: 0, scale: 0.9, y: -10 }}
exitStyle={{ opacity: 0, scale: 0.9, y: -10 }}
opacity={1}
scale={1}
y={0}
backgroundColor="$background"
padding="$4"
borderRadius="$4"
elevation="$2"
>
<Text>Animated content</Text>
</YStack>
)}
</AnimatePresence>
</YStack>
);
}
// Hover and press animations (web + native)
function InteractiveButton() {
return (
<Button
animation="quick"
hoverStyle={{ scale: 1.05, backgroundColor: '$blue6' }}
pressStyle={{ scale: 0.95, opacity: 0.8 }}
focusStyle={{ borderColor: '$blue8' }}
>
Interactive Button
</Button>
);
}Fix 6: Custom Styled Components
import { styled, YStack, Text, GetProps } from 'tamagui';
// Create custom styled components
const Card = styled(YStack, {
backgroundColor: '$background',
borderRadius: '$4',
padding: '$4',
borderWidth: 1,
borderColor: '$borderColor',
// Variants
variants: {
elevated: {
true: {
elevation: '$2',
shadowColor: '$shadowColor',
},
},
size: {
sm: { padding: '$2' },
md: { padding: '$4' },
lg: { padding: '$6' },
},
} as const,
// Default variants
defaultVariants: {
size: 'md',
},
});
// Extract props type
type CardProps = GetProps<typeof Card>;
// Usage
<Card elevated size="lg">
<Text>Custom card component</Text>
</Card>
// Extend further
const FeatureCard = styled(Card, {
borderLeftWidth: 4,
borderLeftColor: '$blue8',
variants: {
type: {
info: { borderLeftColor: '$blue8' },
success: { borderLeftColor: '$green8' },
warning: { borderLeftColor: '$yellow8' },
error: { borderLeftColor: '$red8' },
},
} as const,
});
<FeatureCard type="success" elevated>
<Text>Feature card</Text>
</FeatureCard>Still Not Working?
Components have no styles — TamaguiProvider with a valid config must wrap your app. Without it, token references ($4, $background) resolve to empty values. Import the config from @tamagui/config/v3 for defaults.
“Expected theme to be defined” — the theme referenced by a component or the defaultTheme prop doesn’t exist in the config. Check that light and dark themes are defined. If using custom theme names, ensure they’re in tamaguiConfig.themes.
Styles work on web but not native — some CSS properties don’t exist on React Native (e.g., cursor, userSelect, CSS gradients). Tamagui handles many cross-platform differences, but custom styles may need platform-specific values using $platform-native or $platform-web prefixes.
Animations don’t play — install react-native-reanimated and add the babel plugin. Tamagui uses Reanimated for native animations. Without it, animation props are silently ignored on native.
For related mobile UI issues, see Fix: NativeWind Not Working and Fix: Expo Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: NativeWind Not Working — Styles Not Applying, Dark Mode Broken, or Metro Bundler Errors
How to fix NativeWind issues — Tailwind CSS for React Native setup, Metro bundler configuration, className prop, dark mode, responsive styles, and Expo integration.
Fix: Expo Router Not Working — Routes Not Matching, Layout Nesting Wrong, or Deep Links Failing
How to fix Expo Router issues — file-based routing, layout routes, dynamic segments, tabs and stack navigation, modal routes, authentication flows, and deep linking configuration.
Fix: React Native Paper Not Working — Theme Not Applying, Icons Missing, or Components Unstyled
How to fix React Native Paper issues — PaperProvider setup, Material Design 3 theming, custom color schemes, icon configuration, dark mode, and Expo integration.
Fix: React Navigation Not Working — Screens Not Rendering, TypeScript Errors, or Gestures Broken
How to fix React Navigation issues — stack and tab navigator setup, TypeScript typing, deep linking, screen options, nested navigators, authentication flow, and performance optimization.