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.
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 noneWhat this creates
- An Expo app in
apps/nativeregistered throughindex.jswithregisterRootComponent. - React Navigation with a native stack (
Mainand a modalModalscreen) and bottom tabs (HomeandSettings). - A
MobileUIProviderthat wraps the app inTamaguiProviderwith the@tamagui/configv3 preset. - Jest with the
jest-expopreset and React Native Testing Library, plus one provider test. - A Turborepo workspace with shared
configandenvpackages. No backend, database, or auth.
Generated shape
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
cd my-tamagui-app
npm install
npm run dev:native
npm run testnpm 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:
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:
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:
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:
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-bareis the Expo variant without Uniwind or Unistyles, so it pairs with Tamagui or Gluestack UI.native-uniwindandnative-unistylestie to their matching--mobile-uivalues.--mobile-testing maestroadds Maestro flows, andmaestro-react-native-testing-libraryadds both.- For a generated backend and auth, see Expo App with Hono, tRPC, and Better Auth. The
react-nativeecosystem 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
| Symptom | Check |
|---|---|
No app/ routes directory | Expected; React Navigation registers screens in navigation/native-navigation.tsx. |
| Tamagui styles do not appear | Confirm the screen renders Tamagui components; the generated screens use StyleSheet. |
| Jest fails to transform a new native package | Add the package to transformIgnorePatterns in jest.config.js. |
navigation.navigate("Modal") fails type checks | Type the screen props with the RootStackParamList navigator types. |
Next steps
- Open the Stack Builder.
- Read the React Native ecosystem docs.
- Review the CLI create reference.