GuidesReact Native

Expo App with React Navigation and Tamagui

Create an Expo app with React Navigation stack and tab navigators, the Tamagui provider, Jest, and React Native Testing Library using Better Fullstack.

Updated October 6, 2026exporeact-nativereact-navigationtamagui

Use this stack when you prefer React Navigation's component-based navigators to file-based routing, and want Tamagui and a component test harness in place before the first screen.

npm create better-fullstack@latest my-tamagui-app -- \  --ecosystem react-native \  --frontend native-bare \  --mobile-navigation react-navigation \  --mobile-ui tamagui \  --mobile-storage none \  --mobile-testing react-native-testing-library \  --mobile-push none \  --mobile-ota none \  --mobile-deep-linking none \  --mobile-libraries none

What this creates

  • An Expo app in apps/native registered through index.js with registerRootComponent.
  • React Navigation with a native stack (Main and a modal Modal screen) and bottom tabs (Home and Settings).
  • A MobileUIProvider that wraps the app in TamaguiProvider with the @tamagui/config v3 preset.
  • Jest with the jest-expo preset and React Native Testing Library, plus one provider test.
  • A Turborepo workspace with shared config and env packages. No backend, database, or auth.

Generated shape

txt
my-tamagui-app/
├── package.json
├── turbo.json
├── apps/native/
│   ├── index.js
│   ├── App.tsx
│   ├── app.json
│   ├── jest.config.js
│   ├── metro.config.js
│   ├── navigation/native-navigation.tsx
│   ├── components/
│   │   ├── container.tsx
│   │   ├── header-button.tsx
│   │   ├── mobile-ui-provider.tsx
│   │   └── tabbar-icon.tsx
│   ├── lib/
│   └── __tests__/mobile-ui-provider.test.tsx
└── packages/
    ├── config/
    └── env/

app.json derives the URL scheme, iOS bundle identifier, and Android package from the project name.

Prerequisites

  • Node.js and npm.
  • Expo tooling, plus an iOS simulator, Android emulator, or physical device.

Run and verify

bash
cd my-tamagui-app
npm install
npm run dev:native
npm run test

npm run dev:native runs expo start --clear in apps/native. npm run test runs Jest in the native workspace.

Representative snippets

App.tsx layers the gesture handler, safe area, Tamagui, and navigation providers:

tsx
function AppShell() {
  return (
    <GestureHandlerRootView style={styles.container}>
      <SafeAreaProvider>
        <MobileUIProvider>
          <NavigationContainer>
            <StatusBar style="auto" />
            <RootNavigator />
          </NavigationContainer>
        </MobileUIProvider>
      </SafeAreaProvider>
    </GestureHandlerRootView>
  );
}

The navigators are typed with param lists, so navigation.navigate calls are checked against the declared routes:

tsx
type RootStackParamList = {
  Main: undefined;
  Modal: undefined;
};

export function RootNavigator() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Main" component={Tabs} options={{ headerShown: false }} />
      <Stack.Screen name="Modal" component={ModalScreen} options={{ presentation: "modal" }} />
    </Stack.Navigator>
  );
}

The Modal route is registered, but no screen navigates to it yet. Add a button on Home that calls navigation.navigate("Modal") to open it.

Using Tamagui in screens

The generated screens use React Native View, Text, and StyleSheet. Tamagui is available through the provider but no screen uses its components yet. Replace a screen's primitives with Tamagui components once you pick a design direction:

tsx
import { Button, H2, YStack } from "tamagui";

function HomeScreen() {
  return (
    <YStack flex={1} justifyContent="center" gap="$3" padding="$4">
      <H2>Home</H2>
      <Button>Continue</Button>
    </YStack>
  );
}

Testing

The generated test mocks tamagui and @tamagui/config/v3, renders MobileUIProvider with a child, and asserts the child text is visible:

tsx
test("renders children inside the mobile provider", async () => {
  const screen = await render(
    <MobileUIProvider>
      <Text>Mobile ready</Text>
    </MobileUIProvider>,
  );

  expect(screen.getByText("Mobile ready")).toBeTruthy();
});

The mocks keep the test independent of Tamagui's runtime. Screen tests that render real Tamagui components need the actual provider instead of the mock.

When to choose it

Choose React Navigation when you want navigators defined in code, typed param lists, and explicit screen registration. Choose --mobile-navigation expo-router for file-based routes; the Expo Uniwind guide uses that path.

Compatibility notes

  • --frontend native-bare is the Expo variant without Uniwind or Unistyles, so it pairs with Tamagui or Gluestack UI. native-uniwind and native-unistyles tie to their matching --mobile-ui values.
  • --mobile-testing maestro adds Maestro flows, and maestro-react-native-testing-library adds both.
  • For a generated backend and auth, see Expo App with Hono, tRPC, and Better Auth. The react-native ecosystem itself scaffolds the app only.

Deployment notes

Plan native build profiles, signing credentials, and app identifiers before the first store build. Values read at build time end up in the app bundle, so keep secrets on a server.

Troubleshooting

SymptomCheck
No app/ routes directoryExpected; React Navigation registers screens in navigation/native-navigation.tsx.
Tamagui styles do not appearConfirm the screen renders Tamagui components; the generated screens use StyleSheet.
Jest fails to transform a new native packageAdd the package to transformIgnorePatterns in jest.config.js.
navigation.navigate("Modal") fails type checksType the screen props with the RootStackParamList navigator types.

Next steps

GitHub Sponsors