ReadyNative

Tamagui

Set up Tamagui for UI in an Expo app with ReadyNative: Tamagui 2 (v5 config, optimising compiler) behind @/ui · Expo Go OK

Expo Go: yes

Runs in Expo Go; no dev build needed for this module.

Tamagui 2.7.7 on the @tamagui/config/v5 preset, as the kit in src/components/ui/ (src/ui/tamagui/ before setup). Pick it when you want a typed styling API with variants and a compiler instead of class strings. JS-only, so Expo Go works; animations use the react-native-reanimated you already have.

Setup

bun run setup --ui tamagui
  1. Set your colours and radius in readynative.config.ts / src/theme/tokens.ts.
  2. bun run gen:theme - regenerates tamagui.config.ts.
  3. bun run start -- -c - once, so Metro and Babel pick up the compiler.

Setup installs tamagui, @tamagui/config, @tamagui/animations-reanimated and @tamagui/font-inter (exact 2.7.7) plus the Babel and Metro plugins, and writes babel.config.js (@tamagui/babel-plugin, extraction off in development), metro.config.js (withTamagui) and the generated tamagui.config.ts.

That config is static on purpose - the compiler loads it outside Metro, so no @/ imports. It registers light / dark themes with every colour token as a key ($card, $primaryForeground, $destructiveForeground, $success, $brandAccent…) plus Tamagui's semantic keys (background, color, borderColor…) mapped from them; space and radius extend the v5 scales with the token keys. settings.onlyAllowShorthands is false, so full React Native prop names work. There's no global.css and you never run tamagui generate - TamaguiProvider injects the theme CSS at runtime.

Usage

Screens use the primitives:

import { Box, Button, Screen, Text } from "@/components/ui";

<Screen>
  <Box p={4} gap={2}>
    <Text variant="title">Inbox</Text>
    <Button onPress={refresh}>Refresh</Button>
  </Box>
</Screen>;

Your own components use styled with $token keys:

import { styled, YStack } from "tamagui";

export const Panel = styled(YStack, {
  backgroundColor: "$card",
  borderRadius: "$md",
  variants: { tone: { danger: { backgroundColor: "$destructive" } } } as const,
});

The as const on variants is what types the variant prop.

Inside the kit, RN-backed primitives (Switch, Input's TextInput, Icon, Skeleton, Screen's SafeAreaView) read useTheme(); Tamagui-styled ones use $token keys. ForcedMode is <Theme name="dark"> plus the mode context, so both agree inside the subtree. To add a primitive: add its prop type to src/components/ui/types.ts and export it from src/components/ui/index.ts.

Gotchas

  • jest: Tamagui ships ESM .native.js entries, so jest.config.js transformIgnorePatterns includes |tamagui|@tamagui/.*. No extra mocks - reanimated is already mocked in jest.setup.ts.
  • The compiler writes .tamagui/ at the project root on first build (gitignored). [tamagui] skipped loading 2 module … warning-001 a few times is its benign "could not statically load" notice.
  • Tamagui's Input is typed against the web DOM (disabled, no editable / keyboardType), so the kit's Input wraps a plain RN TextInput in a Tamagui frame. Switch is the platform switch.
  • @tamagui/font-inter is installed but not wired - v5 uses the system fonts. To use Inter, load @tamagui/font-inter/otf/*.otf with expo-font and pass createInterFont() as fonts.body / fonts.heading in the generator.
  • React Compiler stays on: tsc, jest and expo export pass. Only a device run proves the reanimated press animations; if they misbehave, set "app": { "expo": { "experiments": { "reactCompiler": false } } } in module.json.
  • Web is best-effort: outputCSS is only honoured by the tamagui generate CLI; runtime CSS injection is what ships.
  • Core src/lib/utils.ts still imports clsx / tailwind-merge (nativewind5 deps); it typechecks only while those linger in node_modules.

Remove it

Every app needs a ui option, so there is no none to fall back to - you switch instead. While modules/ exists (a tree set up with --keep-modules), one call does it:

bun run setup --ui nativewind4 --yes --keep-modules

A finalized tree can't switch: clone a fresh copy of your tier repo, run setup there with the new option and port your code across - see Can I change a module after setup?.

Reference

Everything below is generated from modules/ui/tamagui/module.json - the same file bun run setup reads, so it is what actually lands in your repo.

Install

bun run setup --ui tamagui

Module id: ui/tamagui.

Dependencies

PackageVersionKind
@tamagui/animations-reanimated2.7.7dependency
@tamagui/config2.7.7dependency
@tamagui/font-inter2.7.7dependency
tamagui2.7.7dependency
@tamagui/babel-plugin2.7.7devDependency
@tamagui/metro-plugin2.7.7devDependency

Providers

Rendered in src/providers.tsx (lower order = outermost):

OrderProviderFrom
50ThemeProvider as UiThemeProvider@/ui

After setup

  1. Run bun run start -- -c once so Metro/Babel pick up the Tamagui compiler.

Files

23 files copied to the project root
  • babel.config.js
  • metro.config.js
  • tamagui.config.ts
  • src/ui/tamagui/box.tsx
  • src/ui/tamagui/button.tsx
  • src/ui/tamagui/card.tsx
  • src/ui/tamagui/divider.tsx
  • src/ui/tamagui/empty-state.tsx
  • src/ui/tamagui/error-state.tsx
  • src/ui/tamagui/icon.tsx
  • src/ui/tamagui/index.ts
  • src/ui/tamagui/input.tsx
  • src/ui/tamagui/list.tsx
  • src/ui/tamagui/loading.tsx
  • src/ui/tamagui/pressable.tsx
  • src/ui/tamagui/row.tsx
  • src/ui/tamagui/screen.tsx
  • src/ui/tamagui/sheet.tsx
  • src/ui/tamagui/skeleton.tsx
  • src/ui/tamagui/switch.tsx
  • src/ui/tamagui/text.tsx
  • src/ui/tamagui/theme-provider.tsx
  • src/ui/tamagui/toast.tsx

On this page

Get ReadyNative