Caracal Starter
Start Here

Project Structure

Complete guide to the Caracal starter project structure, file organization, and architectural patterns.

The Caracal starter follows a well-organized, scalable structure designed for production React Native applications. This guide explains the purpose of each directory and how they work together.

Overview

If you open the project in your editor, you'll see this structure:

src/
├── api/                          # API layer and data fetching
│   ├── common/                   # Shared API utilities
│   │   ├── api-provider.tsx
│   │   └── client.tsx
│   ├── index.tsx
│   └── types.ts
├── app/                          # Expo Router routes (file-based routing)
│   ├── (components)/             # 30 component showcase routes
│   │   ├── _layout.tsx
│   │   ├── accordion.tsx
│   │   ├── button.tsx
│   │   ├── card.tsx
│   │   ├── component-list.tsx
│   │   └── ...
│   ├── (home)/                   # Main app routes
│   │   ├── _layout.tsx
│   │   ├── index.tsx
│   │   └── settings.tsx
│   ├── _layout.tsx               # Root layout with providers
│   ├── +html.tsx
│   └── [...messing].tsx          # 404 catch-all route
├── components/                   # Reusable components
│   ├── ui/                       # Core UI components (custom)
│   │   ├── focus-aware-status-bar.tsx
│   │   ├── image.tsx
│   │   ├── index.tsx
│   │   ├── input.tsx             # ControlledInput
│   │   ├── list.tsx              # FlashList wrapper
│   │   ├── modal.tsx
│   │   ├── text.tsx              # i18n-enabled Text
│   │   ├── utils.tsx
│   │   └── icons/
│   ├── theme-toggle.tsx
│   └── logo.tsx
├── lib/                          # Core utilities and configuration
│   ├── auth/
│   │   ├── index.tsx
│   │   └── utils.tsx
│   ├── contexts/
│   │   └── app-theme-context.tsx
│   ├── hooks/
│   │   ├── index.tsx
│   │   ├── use-is-first-time.tsx
│   │   └── use-selected-theme.tsx
│   ├── i18n/
│   │   ├── index.tsx
│   │   ├── resources.ts
│   │   └── react-i18next.d.ts
│   ├── env.js                    # Client env re-export
│   ├── storage.tsx               # MMKV wrapper
│   ├── test-utils.tsx
│   └── utils.ts
├── themes/                       # Uniwind theme tokens (CSS)
│   └── sky.css
├── translations/                 # i18n resource files
│   ├── ar.json
│   └── en.json
└── types/                        # Global TypeScript types
    ├── index.ts
    └── image.d.ts
env.js                            # Environment variable validation (Zod)
app.config.ts                     # Expo configuration
package.json
tsconfig.json
global.css                        # Tailwind + Uniwind CSS entry
uniwind-types.d.ts                # Auto-generated Uniwind typings
babel.config.js
metro.config.js
jest.config.js
eslint.config.mjs

Directory Deep Dive

src/app/ - File-Based Routing

The app/ directory contains all routes using Expo Router with file-based routing and typed routes enabled.

Route Groups

The project uses route groups (directories wrapped in parentheses) to organize routes without affecting URLs:

(home) - Main Application Routes

Contains the primary app screens with native tab navigation:

  • _layout.tsx - Tab navigator using expo-router/unstable-native-tabs
  • index.tsx - Home/Feed screen (main landing page)
  • settings.tsx - Settings screen

Navigation: These appear as tabs at the bottom of the app.

(components) - Component Showcase Routes

Contains 30 interactive component examples demonstrating HeroUI Native components:

  • _layout.tsx - Stack navigator for component screens
  • 29 component showcase screens (accordion, button, card, etc.)
  • component-list.tsx - Navigation hub for all showcases

Purpose: Educational resource and reference implementation for developers learning HeroUI Native components.

The (components) route group serves as a living documentation system. Run the app and explore these screens to see HeroUI Native components in action with real code examples.

Root Layout (_layout.tsx)

The root layout establishes the provider hierarchy critical for the app to function:

// Provider order (inside to outside):
// 1. GestureHandlerRootView - Gesture support
// 2. KeyboardProvider - Keyboard handling
// 3. AppThemeProvider - Theme management (light/dark)
// 4. HeroUINativeProvider - UI component context
// 5. APIProvider - React Query setup
// 6. BottomSheetModalProvider - Bottom sheet support

src/components/ - Reusable Components

components/ui/ - Custom UI Components

Contains 6 custom components designed for specific needs:

ComponentPurposeFile
Imageexpo-image with Uniwind supportimage.tsx
Texti18n-enabled with RTL supporttext.tsx
ControlledInputForm input with react-hook-forminput.tsx
ListFlashList wrapper with EmptyListlist.tsx
ModalBottom sheet modal wrappermodal.tsx
FocusAwareStatusBarRoute-aware StatusBarfocus-aware-status-bar.tsx

Important: For buttons, cards, dialogs, and other standard UI, use HeroUI Native components instead.

src/api/ - Data Fetching Layer

API calls organized using React Query with react-query-kit for type safety:

api/
├── common/
│   ├── api-provider.tsx    # QueryClient provider
│   └── client.tsx          # Axios instance (baseURL from Env.API_URL)
├── index.tsx               # API exports
└── types.ts               # API type definitions

Pattern:

import { createQuery } from 'react-query-kit';
import { client } from './common/client';

export const usePosts = createQuery({
  queryKey: ['posts'],
  fetcher: () => client.get('/posts'),
});

See Data Fetching guide for complete patterns.

src/lib/ - Core Utilities

The lib/ directory contains framework-agnostic utilities that can be shared across projects.

lib/auth/ - Authentication State

Zustand-based auth state with MMKV persistence:

// src/lib/auth/index.tsx
type AuthState = {
  token: TokenType | null;
  status: 'idle' | 'signOut' | 'signIn';
  signIn: (data: TokenType) => void;
  signOut: () => void;
  hydrate: () => void;
};

// Usage with selectors pattern
const token = useAuth.use.token();
const signIn = useAuth.use.signIn();

See Authentication guide for details.

lib/hooks/ - Custom Hooks

  • use-is-first-time.tsx - First-time user detection
  • use-selected-theme.tsx - Current theme selection
  • use-accessability-info.ts - Accessibility utilities

lib/i18n/ - Internationalization

i18next configuration and TypeScript types:

i18n/
├── index.tsx          # i18next setup
├── resources.ts       # Translation imports
└── react-i18next.d.ts # TypeScript definitions

See Internationalization guide for usage.

lib/storage.tsx - MMKV Storage

Type-safe wrapper around react-native-mmkv:

import { getItem, setItem, removeItem } from '@/lib/storage';

// Persist data
await setItem('user', { id: 1, name: 'John' });

// Retrieve data
const user = getItem<User>('user');

// Remove data
await removeItem('user');

lib/env.js - Environment Variables

Re-exports client environment variables from root env.js:

import { Env } from '@env'; // Resolves to src/lib/env.js

const apiUrl = Env.API_URL;
const appName = Env.NAME;

See Environment Variables guide for configuration.

lib/utils.ts - General Utilities

Shared utilities including:

  • createSelectors - Zustand selectors pattern for performance
  • Other helper functions

src/translations/ - Translation Resources

JSON files for each supported locale:

translations/
├── en.json  # English translations
└── ar.json  # Arabic translations

Format:

{
  "welcome": {
    "title": "Welcome to Caracal",
    "subtitle": "A React Native starter"
  }
}

Validation: ESLint ensures:

  • Identical keys across all language files
  • Sorted keys alphabetically
  • Valid JSON syntax

src/types/ - Global TypeScript Types

Shared type definitions used across the app:

types/
├── index.ts    # General types
└── image.d.ts  # Image module declarations

Root Configuration Files

env.js - Environment Variable System

Critical file that loads and validates environment variables using Zod schemas:

// Defines two schemas:
const client = z.object({
  APP_ENV: z.enum(['development', 'staging', 'production']),
  API_URL: z.string(),
  // ... client variables
});

const buildTime = z.object({
  EXPO_ACCOUNT_OWNER: z.string(),
  EAS_PROJECT_ID: z.string(),
  // ... build-time only variables
});

Environment files: .env.development, .env.staging, .env.production

See Environment Variables guide for complete setup.

app.config.ts - Expo Configuration

Dynamic Expo configuration with:

  • Multi-environment support (dev/staging/prod)
  • React Compiler enabled (experimental)
  • Typed routes enabled
  • App icon badges for non-production builds
experiments: {
  typedRoutes: true,        // Type-safe navigation
  reactCompiler: true,      // Automatic optimization
}
scheme: 'caracalApp',         // Deep linking

The New Architecture is always enabled on Expo SDK 55+ and has no config flag.

global.css - Uniwind Configuration

Uniwind v1.10.0 uses TailwindCSS v4, which is configured in CSS rather than a tailwind.config.js file. global.css is the entry point:

@import "tailwindcss";
@import "uniwind";

@import "heroui-native/styles";
@source './node_modules/heroui-native/lib';

@import "./src/themes/sky.css";

Design tokens (colors, fonts, radius) live in src/themes/sky.css as CSS custom properties, split into @variant light and @variant dark blocks.

Metro is what wires this up, in metro.config.js:

module.exports = withUniwindConfig(config, {
  cssEntryFile: './global.css',
  dtsFile: './uniwind-types.d.ts',
});

See UI & Theming guide for styling.

Testing & Quality

  • jest.config.js - Jest configuration with jest-expo preset
  • eslint.config.mjs - ESLint flat config (v9) with React Compiler plugin
  • .prettierrc.js - Code formatting with TailwindCSS plugin

Key Architectural Patterns

1. Route Groups for Organization

Route groups organize routes without affecting URLs:

app/
├── (home)/        # Tab navigation
│   └── index.tsx  # URL: /
└── (components)/  # Stack navigation
    └── button.tsx # URL: /button (not /components/button)

2. Absolute Imports

All imports use @/ prefix for cleaner code:

// ✅ Good
import { Image, Text } from '@/components/ui';
import { useAuth } from '@/lib/auth';
import { usePosts } from '@/api';

// ❌ Avoid
import { Image } from '../../../components/ui';

Configuration: Set up in tsconfig.json and babel.config.js.

3. Provider Hierarchy

The app wraps components in a specific order (see src/app/_layout.tsx):

GestureHandlerRootView
  └─ KeyboardProvider
      └─ AppThemeProvider
          └─ HeroUINativeProvider
              └─ APIProvider (React Query)
                  └─ BottomSheetModalProvider
                      └─ App Content

Why this order?

  • Gestures must wrap everything
  • Keyboard handling before UI
  • Theme before styled components
  • API provider before data-fetching components
  • Bottom sheet last for modals to overlay everything

4. Component Showcase System

The (components) route group provides:

  • 30 interactive examples of HeroUI Native components
  • Live demonstrations with code patterns
  • Learning resource for developers
  • Reference implementation for component usage

How to use:

  1. Run the app: pnpm start
  2. Navigate to Components section
  3. Explore each component showcase
  4. View source code in src/app/(components)/

5. Separation of Concerns

DirectoryPurposeImport From
components/ui/Custom components@/components/ui
HeroUI NativeStandard UI libraryheroui-native
lib/Framework-agnostic utils@/lib/*
api/Data fetching@/api
app/Routes onlyDon't import from here

Common Use Cases

Adding a New Screen

  1. Create file in appropriate route group:

    src/app/(home)/profile.tsx
  2. Export default component:

    export default function ProfileScreen() {
      return <View>...</View>;
    }
  3. Navigation handled automatically by Expo Router

Adding a New Component

  1. For standard UI: Use HeroUI Native components
  2. For custom needs: Create in src/components/
  3. For reusable UI utilities: Add to src/components/ui/

Adding a New API Endpoint

  1. Create in src/api/:

    // src/api/users.ts
    export const useUsers = createQuery({
      queryKey: ['users'],
      fetcher: () => client.get('/users'),
    });
  2. Export from src/api/index.tsx

  3. Use in components:

    import { useUsers } from '@/api';
    
    const { data, isLoading } = useUsers();

Project Philosophy

The structure follows these principles:

  1. Production-ready: Organized for scale and maintainability
  2. Clear separation: Each directory has a single responsibility
  3. Reusability: Lib utilities are framework-agnostic
  4. Living documentation: Component showcases as learning tool
  5. Type safety: TypeScript throughout with strict mode
  6. Minimal dependencies: Only essential, well-maintained libraries

Opinionated but flexible: This structure represents best practices for React Native development. Feel free to adapt it to your project's needs, but the core organization principles should remain.

What's Next?

Now that you understand the project structure, explore:


Quick Reference:

# View file tree in terminal
tree -L 3 -I 'node_modules|.expo|android|ios|dist'

# Count component showcases
ls src/app/\(components\)/*.tsx | wc -l  # 30 files

On this page