A starter mobile app built with Expo and React Native. Includes Expo Router with native tabs, NativeWind, i18n, Zustand for state management, React Query for data fetching, Zod for form validation, and Storybook for on-device component development. Clone this repo to kick off new projects without repeating the same setup.
- Clone the repository.
- Run
bun install. - Update
name,slug, andschemeinapp.json, andnameinpackage.json, for your app. - Replace app icons and splash in
assets/images/and adjustapp.jsonbranding as needed. - On GitHub you can use Use this template to create a new repo from this project.
├── app/ # App routes (file-based routing with expo-router)
│ ├── (tabs)/ # Tab navigation screens
│ │ ├── (home)/ # Home tab stack
│ │ ├── (explore)/ # Explore tab stack
│ │ ├── (profile)/ # Profile tab stack
│ │ └── _layout.tsx # Tab bar configuration
│ ├── _layout.tsx # Root layout configuration
│ └── +not-found.tsx # 404 screen
├── assets/ # Static assets (images, fonts)
├── components/ # Reusable UI components
│ ├── Button/ # Button component with variants
│ ├── Input/ # Input component with validation
│ ├── Typography/ # Typography component
│ └── LoginForm/ # Example form with validation
├── hooks/ # Custom React hooks
│ └── useI18n.ts # i18n hook wrapper
├── i18n/ # Internationalization setup
│ ├── index.ts # i18n configuration
│ └── locales/ # Translation files (en, es)
├── lib/ # Utilities and schemas
│ └── schemas.ts # Zod validation schemas (i18n-ready)
├── providers/ # React context providers
│ └── query.tsx # React Query provider
├── stores/ # Zustand state stores
│ └── auth.ts # Authentication store
├── .rnstorybook/ # Storybook configuration
│ ├── main.ts # Stories glob and addons config
│ ├── preview.tsx # Global decorators and parameters
│ └── index.tsx # Storybook UI root
├── global.css # Global styles with Tailwind
└── app.json # Expo configuration
| Command | Description |
|---|---|
bun start |
Start the Expo development server |
bun ios |
Run on iOS Simulator |
bun android |
Run on Android Emulator |
bun web |
Run in the web browser |
bun storybook |
Start Expo with the Storybook UI |
bun storybook:generate |
Regenerate the stories index file |
bun type-check |
Run TypeScript compiler in check-only mode |
bun lint |
Run Biome linter |
bun lint:fix |
Run Biome linter with auto-fix |
bun validate |
Run type-check and lint:fix in sequence |
- Framework: Expo SDK 54
- Navigation: Expo Router with native tabs
- Styling: NativeWind v5
- Animations: React Native Reanimated
- Internationalization: i18next + react-i18next + expo-localization
- State Management: Zustand
- Data Fetching: TanStack Query (React Query)
- Form Validation: React Hook Form + Zod
- Language: TypeScript
- Component Development: Storybook for React Native (on-device)
- Linting: Biome
The app supports multiple languages using i18next and react-i18next. Translation files are in i18n/locales/:
en.json– Englishes.json– Spanish (default fallback)
The app detects the device locale via expo-localization.
All translation keys use snake_case for consistency:
{
"screens": {
"profile": {
"edit_username": "Edit Username",
"display_name": "User"
}
},
"validation": {
"username": {
"min": "Username must be at least 3 characters"
}
}
}Zod validation schemas are factory functions that receive the t function from i18next, allowing error messages to be translated:
import { useI18n } from "@/hooks/useI18n";
import { createUsernameSchema } from "@/lib/schemas";
function MyComponent() {
const { t } = useI18n();
const schema = createUsernameSchema(t);
// Use with react-hook-form's zodResolver
}This project uses NativeWind v5, which brings Tailwind CSS to React Native. Use Tailwind utility classes in your components:
<View className="flex-1 items-center justify-center bg-white dark:bg-black">
<Text className="text-xl font-bold text-gray-900 dark:text-white">Hello World</Text>
</View>The app uses Zustand for lightweight state management. Stores are in the stores/ directory:
import { useAuthStore } from "@/stores";
function MyComponent() {
const { user, login, logout } = useAuthStore();
}The project includes Storybook for React Native for developing and previewing components in isolation, directly on the device or simulator.
bun storybookThis sets EXPO_PUBLIC_STORYBOOK_ENABLED=true and starts Expo. The root layout detects the flag and renders the Storybook UI instead of the normal app.
Each story file lives inside its component folder with the .stories.tsx extension:
components/
├── Button/
│ ├── Button.tsx
│ └── Button.stories.tsx
├── Input/
│ ├── Input.tsx
│ └── Input.stories.tsx
└── Typography/
├── Typography.tsx
└── Typography.stories.tsx
Stories follow the standard Component Story Format (CSF):
// components/Button/Button.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./Button";
const meta: Meta<typeof Button> = {
title: "Components/Button",
component: Button,
argTypes: {
variant: {
control: "select",
options: ["primary", "secondary", "outline", "ghost"],
},
},
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = {
args: {
children: "Primary Button",
variant: "primary",
},
};The following on-device addons are configured in .rnstorybook/main.ts:
- Controls – tweak component props via the Storybook panel
- Actions – log callback events (e.g.
onPress) - Backgrounds – toggle between light and dark backgrounds
- Expo Documentation
- Expo Router Documentation
- NativeWind Documentation
- i18next Documentation
- Zustand Documentation
- TanStack Query Documentation
- Zod Documentation
- Storybook for React Native
- Biome Documentation
MIT