preprounds

03 — Design System & Screen Specifications

1. Design Principles

  1. Coaching app, not hospital chart. No clinical blue/white, no stethoscope/cross iconography — this is a leadership tool, not a medical one.
  2. Restraint over density. One decision at a time, no dashboards-of-everything.
  3. Encouraging tone throughout copy — never clinical/cold, never falsely gamified about a real workplace problem.
  4. Accessible by default — this audience is often reading one-handed, on their feet, between tasks.

2. Color Tokens

Put these in constants/theme.ts as the single source of truth — never hard-code a hex value inside a component.

export const colors = {
  primary: '#1F6F63',      // deep teal — brand, primary buttons, active states
  accent: '#E07A5F',       // warm terracotta — CTAs, highlights, "practice today" card
  background: '#FBF7F0',   // soft cream — screen background
  surface: '#FFFFFF',      // cards, sheets
  textPrimary: '#26313A',  // charcoal
  textSecondary: '#6B7580',// slate gray
  success: '#7FA37A',      // sage green — positive feedback, "what landed"
  alert: '#D99A4E',        // muted amber — escalation flags. Never use red; red reads as
                            // clinical-alarm in this context and works against the brand.
  border: '#E7E0D4',       // subtle card/divider borders
  disabled: '#C9C2B4',
} as const;

Contrast check before finalizing: verify accent (#E07A5F) on background (#FBF7F0) meets 4.5:1 for body text; if a component needs accent-colored text at small sizes, darken the accent for that specific text use rather than changing the token globally.

Resolved (Sep 2026): canonical Tailwind/NativeWind class form. When wiring these into Tailwind’s @theme, use the camelCase key verbatim as the utility suffix — bg-textPrimary, text-textSecondary — not a kebab-case alias (text-text-primary). Do not define both forms. This isn’t a style preference: text-primary (the brand teal) and a kebab-cased text-text-primary (charcoal body text) are one keystroke apart and easy to confuse under time pressure; keeping textPrimary as one unbroken token avoids the collision entirely and matches the TS key above with zero transformation. If a kebab-case alias already exists in global.css, remove it and grep the codebase for any usage before it spreads further.


3. Typography, Spacing, Radius

export const typography = {
  display: { fontFamily: 'Sora_700Bold', fontSize: 28, lineHeight: 34 },
  h1:      { fontFamily: 'Sora_600SemiBold', fontSize: 24, lineHeight: 30 },
  h2:      { fontFamily: 'Sora_600SemiBold', fontSize: 20, lineHeight: 26 },
  body:    { fontFamily: 'Inter_400Regular', fontSize: 16, lineHeight: 24 },
  bodyBold:{ fontFamily: 'Inter_600SemiBold', fontSize: 16, lineHeight: 24 },
  caption: { fontFamily: 'Inter_400Regular', fontSize: 13, lineHeight: 18 },
} as const;

export const spacing = { xs: 4, sm: 8, md: 16, lg: 24, xl: 32, xxl: 48 } as const;
export const radius = { sm: 8, md: 12, lg: 16, pill: 999 } as const;
export const shadow = {
  card: { shadowColor: '#000', shadowOpacity: 0.06, shadowRadius: 8, shadowOffset: { width: 0, height: 2 }, elevation: 2 },
} as const;

Fonts: use @expo-google-fonts/sora and @expo-google-fonts/inter (npx expo install @expo-google-fonts/sora @expo-google-fonts/inter expo-font), loaded once in the root layout before rendering any screen.

Do not hard-code font sizes inline anywhere else in the app. Every text style pulls from typography above so Dynamic Type / accessibility scaling stays consistent.


4. Component Library Setup (gluestack-ui)

gluestack-ui v3+ is CLI-driven, not a traditional import-from-node_modules library:

npx gluestack-ui init          # once, at project start
npx gluestack-ui add button
npx gluestack-ui add card
npx gluestack-ui add input
npx gluestack-ui add badge
npx gluestack-ui add spinner
npx gluestack-ui add avatar

Each add command copies the component’s actual source into components/ui/[component]/. This means the real prop signature for every component is sitting in your own repo after you add it — read that file before using the component, do not guess or assume props from memory. Theme these components using the tokens in §2–3, via NativeWind className props, not inline style overrides.

Component states required for every interactive element: | Element | States to implement | |—|—| | Button | default, pressed (slightly darker/scaled), disabled (reduced opacity, disabled color), loading (spinner replaces label, button stays same size) | | Card (scenario, history item) | default, pressed (subtle scale/opacity) | | Input | default, focused (primary-colored border), error (alert-colored border + helper text) |


5. Screens — Full Specification

Each screen below lists: purpose, components used, and all three required states (loading / empty / error) where applicable, per the convention in 02-ARCHITECTURE-AND-DATA.md §7.

5.1 Onboarding — app/(onboarding)/role.tsx, unit.tsx, notifications.tsx

5.2 Home — app/(tabs)/home.tsx

5.3 Scenario Pre-brief — app/scenario/[id]/prebrief.tsx

5.4 Roleplay — app/scenario/[id]/roleplay.tsx

5.5 Feedback — app/scenario/[id]/feedback.tsx

5.6 History — app/(tabs)/history.tsx

5.7 Paywall — app/paywall.tsx

5.8 Settings — app/(tabs)/settings.tsx


6. Motion

Keep this simple for v1 — do not add react-native-reanimated custom animations unless every P0 feature (per PRD §6) is already done and there’s real time left. Use Expo Router’s default screen transitions as-is. The one exception: the typing indicator on the Roleplay screen and the skeleton-loading shimmer on Home/History, both of which are standard, low-effort additions that meaningfully improve perceived quality.


7. Accessibility Checklist