Skip to content

Mobile UI/UX Design System

A comprehensive design system for the Okta-Mobile app covering visual identity, interaction patterns, component specs, and UX guidelines. This document serves as both a human-readable design reference and an AI-actionable specification -- design-first language and rationale for human readers, with exact hex values, pixel dimensions, spring configs, and reference files so AI agents can implement features accurately.

Last updated: 2026-03-02


1. Design Philosophy

Four principles guide every design decision in the app:

Playful & Premium

Duolingo-inspired: spring animations, rounded shapes, bold identity colors. The app feels like a product people want to use, not a clinical tool they have to use. Springs give motion a physical, bouncy quality. Rounded corners soften the UI. Bold tab colors create personality.

Color as Identity

Each tab owns a color -- its icon, header accent, and in-tab highlights all share it. Users build spatial memory through color: green means sessions, coral means messages. This reduces cognitive load and makes navigation feel instant.

Tactile Feedback

Every touch produces haptic feedback. Buttons bounce with spring physics. Cards depress on press. The app feels responsive and alive under your fingers, not flat and dead.

Clarity over Decoration

Clean layouts, generous whitespace, clear visual hierarchy. No gradients for the sake of gradients, no decorative elements that don't serve function. Every pixel earns its place by improving readability or guiding the eye.


Hard Rules

These rules cause real bugs or broken UX when ignored. They are not optional.

  1. Haptics on every interactive element. Every TouchableOpacity, pressable card, and tappable icon fires a haptic. Missing haptic = broken UX. Use ImpactFeedbackStyle.Light for casual touches, .Medium for significant actions, NotificationFeedbackType.Success for completions.

  2. Tab screen content clears the floating tab bar. Every ScrollView on a tab screen uses useTabBarBottomPadding() for bottom content padding. Without it, the last item hides behind the tab bar.

  3. All user-facing text goes through t(). No hardcoded strings. Every visible word uses a localization key, and that key exists in both EN and RU locale files. See mobile-translations.md.

  4. Data-fetching screens implement all four states: loading, content, empty, error. First load shows a skeleton (not a blank screen). Errors show ErrorState with a retry action. Empty data shows a helpful message with guidance (not "No data found"). Success actions show SuccessToast.

  5. Colors come from the palette only. No ad-hoc hex values. Every color used in a new feature must appear in the Color Palette tables below. If a color isn't listed, it doesn't belong in the app.

  6. Safe areas are always handled. Use useSafeAreaInsets() for top (notch/Dynamic Island) and bottom (home indicator). Screens that ignore safe areas break on modern devices.

  7. New code uses canonical token values. Typography sizes, border radii, shadow levels, and spring configs each have defined defaults below. Use the canonical default; ad-hoc values (e.g., fontSize: 17, borderRadius: 15, damping: 14) are not allowed.


2. Visual Identity

2.1 Color Palette

Colors are organized by intent. Every color in the app has a semantic purpose -- never pick a hex value because it "looks nice."

Brand Color

The tenant-configurable primary color used for sign-in buttons, focused input borders, and branded CTAs.

Token Value Usage
Brand primary #0066CC Sign-in button, input focus border, branded actions

Configured in lib/tenantConfig.ts via primaryColor. All tenants currently use #0066CC.

Tab Identity Colors

Each tab owns a color that persists across its icon, header tint, and in-tab accents.

Tab Name Hex 15% Tint Used For
Dashboard (Sessions) Emerald #10B981 rgba(16,185,129,0.15) Tab icon, pill bg, workout accents
Messages Coral #FF6B6B rgba(255,107,107,0.15) Tab icon, pill bg, message accents
History Gold #FFC800 rgba(255,200,0,0.15) Tab icon, pill bg, streak number
Profile Purple #A560E8 rgba(165,96,232,0.15) Tab icon, pill bg, settings accents

History uses a separate headerColor: #E6B800 for header tinting (the raw gold is too bright on white).

Reference: components/navigation/tabBarConfig.ts -- TAB_CONFIGS array.

Semantic Colors

Intent Hex Usage
Action / success #10B981 Start button, success toast bg, progress fill, done states
AI accent #667EEA Loading spinner bg, retry button, action links, expand text
Warning #F59E0B Streak number, warning icon, caution states
Error #EF4444 Error state icon bg, error toast bg, destructive buttons, missed days

Neutral Scale

Token Hex Usage
Screen background #F8FAFC Full-screen background (login, loading, error)
Card surface #FFFFFF Cards, modals, tab bar, header
Border (default) #E5E7EB Input borders (unfocused), date circles, skeleton placeholders
Border (subtle) #E2E8F0 Input borders on login, toggle container
Input background #F3F4F6 Icon containers, metadata badges, description bg
Surface subtle #F9FAFB Description section bg, loading state bg, error state bg
Text primary #111827 Headings, card titles, bold labels
Text secondary #374151 Body text, subtitles, exercise names
Text muted #6B7280 Captions, descriptions, secondary labels
Text placeholder #9CA3AF Placeholder text, day labels, rest day counts
Icon inactive #64748B Input icons (unfocused), back button, toggle text
Inactive / disabled #94A3B8 Inactive tab icon, disabled button bg

Do: Use semantic colors by intent -- action green for positive actions, error red for destructive ones. Don't: Pick a color because it looks nice. Every color has a meaning.

2.2 Typography

A 9-tier hierarchy covers every text need. System fonts only -- San Francisco on iOS, Roboto on Android. Monospace for AI-generated content: Menlo (iOS), monospace (Android).

Tier Default Allowed Weight Usage
Screen hero 32px 36px (brand emphasis) bold Login title, tenant brand name
Screen title 30px -- 700 Tab screen headers (via HEADER_TITLE_STYLE)
Feature title 22px 28px (section hero) bold Modal titles, streak number
Section header 18px -- bold SectionHeader title, card group labels
Card title 16px 18px (primary CTA label) 600 Card headings, button text, sign-in button
Body 14px 16px (loading message) 500 Descriptions, subtitles, toast messages
Input label 14px -- 600 Form field labels, toggle text, action links
Input text 16px -- 500 Text inside input fields
Caption 12px 11px (day labels), 13px (date text) 600 Exercise meta, rest day counts, timestamps

Reference: HEADER_TITLE_STYLE in components/navigation/tabBarConfig.ts -- fontSize: 30, fontWeight: "700", letterSpacing: 0.3.

Do: Use the established size tiers. If something feels too small, step up one tier. Don't: Invent a new font size -- find the closest tier.

2.3 Iconography

All icons come from the Ionicons library (@expo/vector-icons).

Variant convention: - Filled variant = active, selected, or emphasized (e.g., heart for active Dashboard tab) - Outline variant = inactive, default, or de-emphasized (e.g., heart-outline for inactive tab)

Size tiers:

Size Usage
26px Navigation tab icons
20px Inline icons (inputs, section headers, action buttons)
24px Toast icons
28px Header action buttons
32px Modal/alert hero icons (40px for ErrorState)
64px Empty state illustrations

Do: Use filled icons to indicate selection. Use outline icons for default/inactive states. Don't: Mix icon libraries. Stick to Ionicons throughout the app.


3. Spacing & Rhythm

3.1 Spacing Scale

Context Value Notes
Screen horizontal padding (tab screens) 16px Cards, section headers, content
Screen horizontal padding (auth/form screens) 30px Login, onboarding, form-heavy screens
Component vertical margins 12px 8px (tight, e.g., section header to content), 16px (loose, e.g., between card groups)
Card internal padding 16px Standard for all card types
Card margin between items 12px marginBottom: 12 between workout cards
Card horizontal margin 16px marginHorizontal: 16
List item gaps 8px Exercise preview rows, day columns
Button vertical padding (secondary) 14px Secondary/action buttons
Button vertical padding (primary CTA) 18px Sign-in button, main CTAs
Form field vertical margin 24px marginBottom: 24 between input wrappers
Contact method to inputs gap 32px marginBottom: 32 on toggle container
Modal internal padding 24px Centered dialog padding

3.2 Screen Layouts

Three templates cover every screen in the app.

Scrollable Tab Screen

The default for Dashboard, Messages, History, and Profile.

+---------------------------+
|  Header (white bg,        |
|  left-aligned title 30px) |
+---------------------------+
|                           |
|  ScrollView               |
|  +---------------------+  |
|  | Card                |  |
|  +---------------------+  |
|  +---------------------+  |
|  | Card                |  |
|  +---------------------+  |
|         ...               |
|  (bottom padding for      |
|   tab bar clearance)      |
|                           |
|  +======================+ |
|  || Floating Tab Bar   || |
|  +======================+ |
+---------------------------+
  • Header: white background, title from HEADER_TITLE_STYLE (30px, weight 700)
  • Content: ScrollView with RefreshControl for pull-to-refresh
  • Bottom: padding via useTabBarBottomPadding() so content clears the floating tab bar

Form Screen

Used for login, onboarding, and data entry flows.

+---------------------------+
|  Safe Area Top            |
+---------------------------+
|  Title (32px bold)        |
|  Subtitle (20px, #374151) |
|                           |
|  [icon] [Email input    ] |
|  [icon] [Password  ][eye] |
|                           |
|  [   Primary Button     ] |
|  [   Secondary Link     ] |
|                           |
|  (keyboard pushes         |
|   content up via          |
|   PlatformKeyboardScroll) |
+---------------------------+
  • Horizontal padding: 30px (paddingHorizontal: 30)
  • Background: #F8FAFC
  • Keyboard: uses PlatformKeyboardScroll + KeyboardToolbar for platform-correct behavior
  • Reference: app/login.tsx

Full-Screen Feature

Used for workout execution, video playback, and immersive flows.

+---------------------------+
|  Custom Header / Back     |
+---------------------------+
|                           |
|  Immersive Content        |
|  (no tab bar visible)     |
|                           |
|                           |
+---------------------------+
|  Floating Bottom Button   |
|  (above safe area)        |
+---------------------------+
  • Tab bar is hidden for full-screen features
  • Bottom actions float above the safe area inset
  • Custom header with back navigation

3.3 The Floating Tab Bar

Content on every tab screen must clear the floating tab bar. The bar hovers above the bottom safe area, so scrollable content needs bottom padding to prevent the last item from hiding behind it.

Clearance formula:

bottomPadding = TAB_BAR_HEIGHT (64) + max(safeAreaBottom, TAB_BAR_BOTTOM_MARGIN (8)) + 16

Use the useTabBarBottomPadding() hook from components/navigation/tabBarConfig.ts -- it computes the exact value accounting for device-specific safe area insets.


4. Shape Language

4.1 Border Radius Scale

A progressive scale from sharp utility shapes to full circles.

Shape Default Allowed Used For
Sharp 8px 4px (small badges) Badges, chips, exercise meta containers, skeleton blocks
Soft 12px 16px (input fields) Buttons, toggle segments, action cards
Round 20px 16px (PlanManagementCard) Cards (WorkoutCard), modal content
Modal 20px 24px (bottom sheet top corners) Centered dialogs
Pill 32px -- Floating tab bar
Circle 50% -- Avatars, date dots (19px on 38px), icon containers, loading spinner

4.2 Shadows & Depth

Four elevation levels create visual hierarchy. Always use platform-specific values.

Level iOS Android Used For
Subtle shadowOpacity: 0.05, offset {0,2}, radius 4 elevation: 2 Input fields, back button, small cards
Standard shadowOpacity: 0.15, offset {0,4}, radius 6 elevation: 3 Workout cards, action buttons, toggle active
Prominent shadowOpacity: 0.25, offset {0,10}, radius 20 elevation: 10 Modals, tab bar, logo container
Colored glow shadowColor: brand, opacity: 0.3, offset {0,4}, radius 8 elevation: 8 Sign-in button, loading spinner, retry button, success toast

Allowed variant: colored glow with offset: {0,8}, radius: 16 for primary CTA buttons that need stronger emphasis (e.g., sign-in button in login.tsx).

Examples from the codebase: - Subtle: login input field container (inputFieldContainer style in login.tsx) - Standard: workout card outer container (container style in WorkoutCard.tsx) - Prominent: modal content card (modalContent style in OutstandingWorkoutModal.tsx) - Colored glow: sign-in button (signInButton style in login.tsx)


5. Component Design

Each component section covers: visual anatomy, states, behavior, exact specs, reference file, and do/don't.

5.1 Cards

Anatomy: White surface #FFFFFF, rounded corners, internal padding 16px, platform shadow.

Variants:

Variant Radius Shadow Example
Standard card 20px shadowOpacity: 0.15, radius: 6 / elevation: 3 WorkoutCard
Action card 16px shadowOpacity: 0.10, radius: 3 / elevation: 2 PlanManagementCard

States: - Default: white bg, shadow, normal scale - Pressed: scale to 0.98 via spring (damping: 10), haptic feedback - Loading: skeleton pulse (see Progress Indicators)

Reference: components/WorkoutCard.tsx, components/PlanManagementCard.tsx

Do: Apply shadow to the outer container, overflow hidden on the inner card. Don't: Nest cards inside cards. Keep the hierarchy flat.

5.2 Buttons

Decision table:

Intent Background Text Border Example
Primary action Tenant primary #0066CC White None Sign in, Submit, Save
Success action #10B981 White None Start workout, Continue
Secondary rgba(action,0.1) Action color 1px colored Start button (non-today)
Destructive #EF4444 White None Delete, Remove, Start new plan
Disabled / loading #94A3B8 White None Any button while loading
Ghost / text link Transparent #667EEA None "View all", "Tap to expand", forgot password

Behavior: - Press: scale to 0.98 with spring (damping: 10), spring back to 1.0, fire haptic - Loading: spinner replaces label text, button dimensions stay constant (no layout shift) - Disabled: backgroundColor: #94A3B8, reduced shadow, disabled prop set

Specs: - Primary CTA: paddingVertical: 18, borderRadius: 16, colored shadow glow - Secondary: paddingVertical: 14, borderRadius: 12 - Text: 16px weight 600 (default), 18px bold (primary CTA) - Always include gap: 8 between icon and text when both present

Reference: app/login.tsx (primary CTA), components/WorkoutCard.tsx (secondary), components/PlanManagementCard.tsx (action)

Helpers: Use createPressHandlers(scaleAnim) from lib/designSystem.ts for consistent press feedback. It returns onPressIn/onPressOut handlers that spring-animate the provided Animated.Value between 1.0 and 0.97.

Do: Keep button text to 1-3 words. Use the correct type for the intent. Don't: Use a ghost button for primary actions. Primary actions need visual weight.

5.3 Input Fields

Anatomy: 56px height, 16px border radius, 2px border, icon left (20px), optional action button right.

States:

State Border Icon Color Shadow
Empty #E2E8F0 #64748B Subtle
Focused / filled Tenant primary #0066CC Tenant primary Colored glow opacity: 0.1
Error #EF4444 #EF4444 Red glow

Specs: - Container: flexDirection: "row", alignItems: "center", paddingHorizontal: 16 - Background: white - Label: 14px weight 600, #374151, positioned above the field with 8px bottom margin - Input text: 16px weight 500, #1E293B - Placeholder: #94A3B8 - Icon: 20px, marginRight: 12 - Password: always include eye toggle button (eye-outline / eye-off-outline, 20px)

Reference: app/login.tsx (inputFieldContainer and inputFocused styles)

Do: Use label above the field, not placeholder-as-label. Always show the focused border color change. Don't: Skip the focused state. Users need to know which field is active.

5.4 Modals & Overlays

Decision table:

When to Use Type Visual
Confirmation, alert, simple yes/no Centered dialog animationType: "fade", white card, 20px radius, centered on backdrop
Selection list, action menu, picker Bottom sheet Slides up from bottom, top corners 24px radius, maxHeight 80%
Complex multi-step flow Full-page sheet pageSheet presentation, step transitions with slide animation

Backdrop: rgba(0,0,0,0.5) -- always tap-to-dismiss on backdrop press.

Centered dialog specs: - borderRadius: 20, padding: 24, maxWidth: 400 - Shadow: shadowOpacity: 0.25, shadowRadius: 20, elevation: 10 - Hero icon at top (32px, in a colored circle container) - Title: 22px bold, centered - Buttons stacked vertically with 12px gap

Android: Handle hardware back button to close modals explicitly.

Reference: components/OutstandingWorkoutModal.tsx (centered dialog), components/workout/ExercisesListModal.tsx (bottom sheet/overlay)

5.5 Lists & Content Feeds

  • Tab screen lists: ScrollView with useTabBarBottomPadding() bottom padding + RefreshControl for pull-to-refresh
  • Long data lists: FlatList for performance -- never nest a FlatList inside a ScrollView
  • Item gaps: 8px between exercise rows, 12px between cards
  • Item separators: 1px border #F3F4F6 for flat list items

5.6 Progress Indicators

Loading state decision tree:

Situation Show Why
First page load (no data yet) Skeleton screen User sees content shape, feels faster
Pull-to-refresh / re-fetch Refresh spinner (RefreshControl) User initiated, expects brief wait
Button action (submit, save) Inline text change in button Button shows it's working, no layout shift
Known progress (upload, workout) Progress bar User sees how far along they are
Full-screen data load LoadingState component Animated spinning dots, centered
AI streaming (inline) AIAnswerStreamingModal Animated spinner + rotating status messages + progress bar (no raw data shown)

Skeleton specs: - Placeholder color: #E5E7EB - Opacity pulses: 0.45 to 0.85, 700ms per direction (total cycle 1400ms) - Shape: rounded rectangles matching the content they replace (e.g., 130x30 for title, 38x38 circle for date dots)

LoadingState specs: - Centered on #F9FAFB background - 8 white dots arranged in a circle (80px container, 10px dots for medium size) - Container: #667EEA background, circular, with colored glow shadow - Rotation: 2000ms per revolution, linear easing, infinite repeat - Entrance: scale 0.8 to 1.0 (600ms), opacity 0 to 1 (800ms) - Message: 16px weight 500, #6B7280

Progress bar specs: - Height: 4px (default), 8px (prominent), border radius: 4px - Track: #E5E7EB - Fill: #10B981, animated with timing

AIAnswerStreamingModal specs: - Full-screen inline overlay on #F8FAFC background (replaces the previous modal approach) - Animated spinner: reuses LoadingState pattern -- 8 rotating white dots in #667EEA circle with colored glow - Rotating status messages: cycles every ~4s through t("aiStreaming.analyzing"), t("aiStreaming.reviewing"), t("aiStreaming.designing"), t("aiStreaming.almostThere") -- 16px weight 500, #6B7280 - Progress bar: tracks streamingProgress from useStreamingRequest -- 4px height, #E5E7EB track, #10B981 fill, 4px radius - Streamed content (raw JSON) is accumulated internally for the onComplete callback but never displayed to the user - Entrance: opacity 0→1 (300ms), spinner scales 0.8→1.0 with spring

Reference: components/LoadingState.tsx, components/aiinteraction/AIAnswerStreamingModal.tsx, components/navigation/StreakHeaderWidget.tsx (skeleton loading)

5.7 Toggle / Segmented Controls

Container: gray pill #E2E8F0, 16px border radius, 4px internal padding.

Active segment: - Background: tenant primary color (#0066CC) - Text: white, 14px weight 600 - Border radius: 12px - Shadow: colored glow (shadowColor: primary, shadowOpacity: 0.3, radius: 8, elevation: 4)

Inactive segment: - Background: transparent - Text: #64748B, 14px weight 600

Layout: flexDirection: "row", each segment flex: 1, paddingVertical: 14, centered text + icon (18px) with 8px gap.

Reference: app/login.tsx (contactMethodToggle, toggleOption, and toggleActive styles)

5.8 Section Headers

Title: 18px bold #111827, with optional icon (20px, #6B7280 default, configurable).

Optional subtitle: 14px #6B7280, 2px top margin.

Optional action link: right-aligned, #667EEA text (14px weight 500), chevron-forward icon (16px), row layout with 4px gap.

Container: flexDirection: "row", justifyContent: "space-between", paddingHorizontal: 16, paddingVertical: 12, marginBottom: 8.

Reference: components/SectionHeader.tsx

5.9 Toast Notifications

Position: absolute, top: 60, left: 20, right: 20, zIndex: 1000.

Background: #10B981 for success (error variant uses #EF4444).

Content: row layout -- checkmark icon (24px white) + message text (14px weight 500, white).

Border radius: 12px.

Animation: - Show: translateY from -100 to 0 (spring, damping: 15), scale from 0.8 to 1.0 (spring, damping: 12), opacity 0 to 1 (timing, 300ms) - Hide: reverse with timing (300ms each), then call onHide - Auto-dismiss: 3000ms default

Shadow: colored glow -- shadowColor: #10B981, offset: {0,4}, opacity: 0.3, radius: 8.

Haptic: Haptics.notificationAsync(Success) on show.

Reference: components/SuccessToast.tsx

5.10 Empty States

Layout: centered in scroll area.

Anatomy: - Icon: Ionicons, 64px, muted gray or tab accent color - Title: 18px bold #111827, centered - Subtitle: 14px #6B7280, centered, helpful message that guides to action - Optional CTA button below

Do: Write empty states that guide the user toward action ("No workouts yet -- your therapist will assign your first plan"). Don't: Show a blank screen or generic "No data found."

5.11 Error States

Icon container: 80x80px circle, #EF4444 background, 40px border radius, with colored glow shadow (shadowColor: #EF4444, opacity: 0.3, radius: 8, elevation: 8).

Icon: warning, 40px, white.

Title: 20px bold #111827, centered, 8px bottom margin.

Message: 14px #6B7280, centered, lineHeight: 20, 24px bottom margin.

Animated entrance: scale 0.8 to 1.0 (spring, damping: 15), opacity 0 to 1 (timing, 600ms).

Retry button: #667EEA background, pill shape (25px radius), row layout with refresh icon + text, paddingHorizontal: 24, paddingVertical: 12. Spring bounce on press (scale to 0.95 then back, damping: 10).

Reference: components/ErrorState.tsx

Do: Always offer a retry action. Show a friendly title + helpful message. Don't: Show raw error messages to users. Catch and humanize every error.


6. Motion & Animation

6.1 Animation Principles

  • Springs over linear timing -- things feel physical, not mechanical. Springs have mass and bounce; linear feels robotic.
  • Every animation serves a purpose: feedback (press response), transition (screen/tab change), or delight (completion celebration).
  • Faster for feedback (150-300ms), slower for decoration (600-800ms). The user should never wait for an animation to finish before they can act.

6.2 Motion Vocabulary

Motion Description When
Press feedback Scale down to 0.98, spring back to 1.0 Card tap, button press
Tab transition Colored pill slides with spring, icon scales up to 1.15x Tab switch
Screen entry Fade + directional slide (FadeInRight/Left) Step flows, navigation
Loading to content Skeleton pulse stops, real content fades in Data arrives
Toast Slides down from top with bounce, auto-slides out Success/error notification
Progress fill Smooth timing animation left-to-right Upload, workout progress
Entrance pop Scale from 0.8 to 1.0 with spring Error state, modal content

6.3 Spring Configs

Exact values from the codebase. Use these -- don't invent new spring parameters.

Name Damping Stiffness Feel Used For
Smooth slide 18 200 Smooth, no overshoot Tab pill translateX, color interpolation
Snappy scale 12 250 Quick with slight bounce Icon scale on tab switch
Gentle press 10 (default) Soft cushion Button/card press feedback
Entrance bounce 15 (default) Welcoming pop Error state entrance, toast show
Toast scale 12 (default) Quick pop-in Toast scale animation

Reference: components/navigation/tabBarConfig.ts -- PILL_SLIDE_SPRING, ICON_SCALE_SPRING, COLOR_SPRING.

6.4 Timing Guidelines

Duration Category Examples
150-300ms Fast feedback Opacity fade (toast show/hide), backdrop appear
300-600ms Content transition Error state opacity fade-in (600ms)
600-800ms Decorative entrance Loading state opacity (800ms), scale entrance (600ms)
700ms Skeleton pulse One direction of the pulse cycle (0.45 to 0.85 or back)
1000ms Progress fill Upload bar, workout progress animation
2000ms Continuous loop Loading spinner rotation (one full revolution)

7. Haptic Design

Interaction Haptic Type Examples
Casual touch ImpactFeedbackStyle.Light Tab switch, card press, list item tap, thumbnail press
Significant action ImpactFeedbackStyle.Medium Plan management press, form submit, start workout
Completion NotificationFeedbackType.Success Workout done, upload complete, success toast

Rule: Every interactive element fires a haptic. A tap with no haptic response = broken UX. Test haptics manually on a physical device -- they don't fire in simulators.

Helpers: Use the shared helpers from lib/designSystem.ts instead of calling expo-haptics directly: - hapticLight() — wraps Haptics.impactAsync(ImpactFeedbackStyle.Light) - hapticMedium() — wraps Haptics.impactAsync(ImpactFeedbackStyle.Medium) - hapticSuccess() — wraps Haptics.notificationAsync(NotificationFeedbackType.Success)

Do: Fire haptics on every interactive element. Use Light for casual browsing, Medium for significant actions. Don't: Use Medium/Heavy for casual browsing. Reserve stronger haptics for moments that matter.


8. Accessibility

Concern Requirement
Touch targets Minimum 44x44pt for all interactive elements
Press feedback activeOpacity: 0.8 on all TouchableOpacity (or use Reanimated scale)
Screen reader labels accessibilityLabel on all interactive elements, especially icon-only buttons
Selection state Announce active/selected state to screen readers (tab bar, toggles)
Disabled state Use disabled prop (visual + functional), not just opacity change
Text overflow numberOfLines with expand affordance for long content

9. Platform Considerations

Shadows

iOS uses shadowColor, shadowOffset, shadowOpacity, shadowRadius. Android uses elevation. Always provide both via Platform.select or by setting both properties (RN applies the correct one per platform).

Safe Areas

Always use useSafeAreaInsets() from react-native-safe-area-context for: - Top: notch / Dynamic Island clearance - Bottom: home indicator / navigation bar clearance

Keyboard

See mobile-keyboard-handling.md for the full platform-by-platform keyboard architecture, including PlatformKeyboardScroll and KeyboardToolbar usage.

Back Navigation

  • iOS: swipe-back gesture is handled natively by React Navigation
  • Android: hardware back button must be explicitly handled for overlays and modals (close the overlay, don't navigate back)

10. UX Patterns & Flows

10.1 Error Recovery

Error Context How It Surfaces Recovery
Initial page load fails Full-screen ErrorState Retry button reloads data
Action fails (submit, save) Alert dialog or inline error User can retry the action
Network offline Toast notification Auto-retry when connection returns
Partial data failure Graceful degradation Show what loaded, hide what didn't

10.2 Role-Based Experience

The app supports two roles with different navigation structures:

  • Patient: 4 tabs -- Dashboard (Sessions), Messages, History, Profile
  • Doctor: 3 tabs -- Dashboard, Messages, Profile (History is hidden)

Doctor mode hides default headers; screens manage their own top padding. The tab bar dynamically adjusts pill width based on visible tab count.

See mobile-role-separation.md for the full route separation architecture, RoleGuard, and file structure conventions.


11. New Feature Design Checklist

Work through this list in order when building any new screen or feature.

Setup

  • [ ] Which role? Patient, doctor, or both?
  • [ ] Which screen template? Scrollable tab / form / full-screen feature?
  • [ ] Which tab does it belong to? (determines accent color)

Layout

  • [ ] Screen padding matches the spacing scale (16px tab, 30px form)
  • [ ] Safe areas handled (top and bottom)
  • [ ] Scrollable content has bottom padding for floating tab bar (useTabBarBottomPadding())
  • [ ] ASCII wireframe sketched before building

Visual Design

  • [ ] All colors from the semantic palette -- no ad-hoc hex values
  • [ ] Typography follows the 9-tier hierarchy
  • [ ] Border radii from the radius scale
  • [ ] Shadow level matches component importance
  • [ ] Icons are Ionicons, correct variant (filled/outline) and size tier

Interactions

  • [ ] Buttons use the correct type for their intent (primary/secondary/destructive/ghost)
  • [ ] Press feedback: use createPressHandlers() from lib/designSystem.ts for spring bounce
  • [ ] Haptics: use hapticLight() / hapticMedium() / hapticSuccess() from lib/designSystem.ts
  • [ ] Haptic level matches interaction importance (light/medium/success)
  • [ ] Input fields have all states: empty, focused, filled, error

States

  • [ ] Loading: skeleton for first load, spinner for actions, progress bar for determinate
  • [ ] Empty state: helpful message + icon + optional CTA (not blank screen)
  • [ ] Error state: clear message + retry action (not raw error text)
  • [ ] Success: toast notification for completions

Content

  • [ ] All user-facing text through t() localization -- no hardcoded strings
  • [ ] Keys added to both EN and RU locale files
  • [ ] Text truncated with numberOfLines where overflow is possible

Accessibility

  • [ ] Touch targets minimum 44x44pt
  • [ ] Screen reader labels on icon-only buttons
  • [ ] Disabled buttons use disabled prop
  • [ ] activeOpacity: 0.8 on all TouchableOpacity

Polish

  • [ ] Animations use springs (not linear timing) for interactive feedback
  • [ ] Spring configs from the reference table (damping/stiffness values)
  • [ ] Haptics fire on every interactive element (test on physical device)
  • [ ] Test on both iOS and Android
  • [ ] Test with both EN and RU language

12. Key Reference Files

File What It Defines
components/navigation/tabBarConfig.ts Tab colors, dimensions, spring configs, useTabBarBottomPadding()
lib/tenantConfig.ts Tenant brand color, name, logo, app type
components/LoadingState.tsx Full-screen animated loading pattern
components/ErrorState.tsx Full-screen error with animated entrance and retry
components/SuccessToast.tsx Toast notification with spring animation and haptic
components/SectionHeader.tsx Section header with optional icon, subtitle, action link
components/WorkoutCard.tsx Card pattern with press feedback, skeleton, thumbnails
components/PlanManagementCard.tsx Action card pattern with haptic feedback
lib/designSystem.ts Shared haptic helpers (hapticLight, hapticMedium, hapticSuccess) + spring press handler (createPressHandlers)
components/aiinteraction/AIAnswerStreamingModal.tsx AI streaming loading experience -- spinner, rotating status messages, progress bar
app/login.tsx Input fields, buttons, segmented control, form layout
components/workout/PatientFeedbackModal.tsx Multi-step modal with step transitions
components/OutstandingWorkoutModal.tsx Centered confirmation dialog pattern
components/workout/ExercisesListModal.tsx Bottom sheet / overlay modal pattern
components/navigation/StreakHeaderWidget.tsx Skeleton loading, streak visualization, day dots

All paths relative to Okta-Mobile/.


Document Covers
mobile-navigation-design.md Floating tab bar, headers, StreakHeaderWidget -- full navigation spec
mobile-keyboard-handling.md Keyboard architecture, platform-specific handling, component decision matrix
mobile-role-separation.md Patient/doctor route separation, RoleGuard, file structure
mobile-workout-flow.md Workout screen flow, HUD variants, upload queue
mobile-translations.md Localization guide, key naming, variable interpolation
feature-flags.md Feature flag system for tenant-level feature gating
streaks.md Streak data model, API, frontend rendering

14. Future Considerations

  • Design Tokens File: All colors are manually matched today. A central tokens.ts file would prevent drift across components and enable runtime theming.
  • Dark Mode: Every color is hardcoded for light mode. A dark mode pass needs updated backgrounds, card surfaces, text colors, and contrast verification against WCAG AA.
  • Shared Component Library: LoadingState, ErrorState, SuccessToast, SectionHeader are good candidates for formalization into a shared component package with typed props and Storybook stories.
  • RTL Support: EN and RU are both LTR. Adding Hebrew, Arabic, or other RTL languages would require layout direction work across all screens.