Skip to content

Latest commit

 

History

History
225 lines (177 loc) · 8.3 KB

File metadata and controls

225 lines (177 loc) · 8.3 KB

Expo Template – Native Tabs, NativeWind, i18n

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.

Using This Template

  1. Clone the repository.
  2. Run bun install.
  3. Update name, slug, and scheme in app.json, and name in package.json, for your app.
  4. Replace app icons and splash in assets/images/ and adjust app.json branding as needed.
  5. On GitHub you can use Use this template to create a new repo from this project.

Project Structure

├── 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

Available Scripts

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

Tech Stack

Internationalization (i18n)

The app supports multiple languages using i18next and react-i18next. Translation files are in i18n/locales/:

  • en.json – English
  • es.json – Spanish (default fallback)

The app detects the device locale via expo-localization.

Translation Keys Convention

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"
    }
  }
}

Internationalized Validation Schemas

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
}

Styling with NativeWind

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>

State Management with Zustand

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();
}

Storybook

The project includes Storybook for React Native for developing and previewing components in isolation, directly on the device or simulator.

Running Storybook

bun storybook

This 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.

Writing Stories

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",
  },
};

Addons

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

Learn More

License

MIT