Frontend Guidelines (React Native / Expo)
Stack
- React Native with Expo SDK
- Expo Router (file-based navigation)
- TypeScript (strict mode)
- Custom design system (no external UI libraries)
- i18next for translations (DE, EN minimum)
- Zustand for global state (keep it simple)
- TanStack React Query for API state
Project Structure
frontend/
├── app/ # Expo Router screens (file-based routing)
│ ├── (tabs)/ # Tab navigator
│ ├── (auth)/ # Auth screens
│ └── _layout.tsx # Root layout
├── components/ # Shared, reusable components
│ ├── ui/ # Design system primitives (Button, Card, Input)
│ └── {feature}/ # Feature-specific components
├── lib/
│ ├── api/ # API client (typed, generated from OpenAPI)
│ ├── hooks/ # Shared hooks
│ ├── stores/ # Zustand stores
│ └── i18n/ # Translation files
├── constants/ # Design tokens, config
└── assets/ # Images, fonts
Conventions
- One component per file
- Colocate styles with components (
StyleSheet.create)
- Use design tokens from
constants/theme.ts
- Every screen handles: loading, error, empty, and success states
- Offline-first: cache API responses, show stale data with refresh indicator
TypeScript
- Strict mode enabled
- No
any type
- Props interfaces:
{ComponentName}Props
- API types generated from OpenAPI spec
State Management
- Server state: TanStack React Query (caching, refetching, optimistic updates)
- Global client state: Zustand (auth, user preferences, theme)
- Local component state: useState/useReducer
- Avoid Redux — keep it simple
Internationalization
- All user-facing strings via i18next
- Key format:
{screen}.{section}.{label} (groups.list.emptyTitle)
- Default language: German (DE)
- Required languages: DE, EN
- Never hardcode strings in components
Accessibility
- Every interactive element has an accessible label
- Touch targets: minimum 44x44 points
- Color contrast: WCAG AA minimum
- Screen reader support: meaningful labels, not “Button 1”
- Test with VoiceOver (iOS) and TalkBack (Android)
- Lazy load screens (Expo Router does this by default)
- Use
FlatList for long lists (never ScrollView with .map())
- Optimize images: use WebP, appropriate sizes
- Minimize re-renders: memoize expensive computations