Skip to content

Mobile Navigation Design System

Overview

The mobile app uses a Duolingo-inspired navigation system β€” a floating tab bar with animated indicators and styled screen headers. The design prioritizes a modern, playful, premium feel with each tab having its own distinct identity color.

Color Palette

Each tab has a dedicated accent color used consistently across the tab bar icon, header title, and any tab-specific UI accents.

Tab Accent Color Hex Pill Tint (15% opacity)
Dashboard (Sessions) Green #10B981 rgba(16,185,129,0.15)
Messages Coral #FF6B6B rgba(255,107,107,0.15)
History Gold #FFC800 rgba(255,200,0,0.15)
Profile Purple #A560E8 rgba(165,96,232,0.15)

Other colors: - Inactive icon: #94A3B8 (slate gray) - Header/tab bar background: #FFFFFF - Fallback text: #111827 (near-black)

Design decision: All header titles use black text for consistency and readability. Accent colors are reserved for tab bar icons and header tint (back buttons, action icons).

Floating Tab Bar

Visual Design

  • Floating: Positioned absolutely above the safe area bottom, not attached to screen edges
  • Rounded: Border radius 32px, creating a pill-shaped container
  • Horizontal margins: 20px from each side of the screen
  • Height: 64px
  • Background: White with platform-appropriate shadow (iOS: shadowOffset/radius, Android: elevation 8)
  • Icons only: No text labels β€” clean, icon-driven navigation

Active Indicator (Pill)

A colored pill slides behind the active tab icon: - Height: 44px with 22px border radius - Width: Computed dynamically based on bar width / visible tab count, with 8px horizontal padding - Color: Interpolated between tab tint colors during transition - Animation: Spring-based translateX (damping: 18, stiffness: 200)

Icon Behavior

  • Active: Filled icon variant (e.g., heart), scaled to 1.15x, colored with tab accent
  • Inactive: Outline icon variant (e.g., heart-outline), scale 1x, slate gray
  • Scale animation: Spring (damping: 12, stiffness: 250)
  • Size: 26px

Icon Mapping

Tab Filled Outline
Dashboard heart heart-outline
Messages chatbubbles chatbubbles-outline
History time time-outline
Profile person-circle person-circle-outline

Interactions

  • Haptic feedback: Light impact on every tab press (Haptics.impactAsync(Light))
  • Keyboard hiding: Tab bar slides down and fades out when the keyboard opens (Messages tab input), springs back when keyboard closes

Doctor Mode

Doctor users see only 3 tabs (History is hidden via href: null). The tab bar dynamically adjusts β€” pill width recalculates and positions correctly for 3 items instead of 4.

Screen Headers

Global Style

  • Background: White (#FFFFFF), no shadow/border
  • Title alignment: Left-aligned (Duolingo style)
  • Title font: Size 30, weight 800 (extra-bold), letter spacing 0.3
  • Title color: Black (#000000)
  • Shadow: Explicitly hidden (headerShadowVisible: false, Android elevation 0)

Per-Screen Details

  • Dashboard: Patient mode replaces the entire default header with a custom header component (StreakHeaderWidget) that renders the "Sessions" title, streak counter, and a week calendar. No default header options (headerTintColor, headerTitleStyle) are set β€” the widget handles all styling. Doctor mode falls through to headerShown: false.
  • Messages: Black title. Has a "+" button in headerRight (Ionicons add, size 28, colored via getTabColor("messages") to match tab accent).
  • History: Black title. Patient-only tab.
  • Profile: Black title.
  • Doctor mode: All headers hidden (headerShown: false). Doctor screens manage their own top safe area padding.

Streak Header Widget

The StreakHeaderWidget fully replaces the default React Navigation header on the Dashboard screen (patient mode only) using the header prop. It manages its own safe area insets, background, and layout, providing an always-visible, glanceable streak and weekly calendar without consuming scroll space.

Layout (two rows)

Row 1: [Sessions]                    [πŸ”₯ 12]
Row 2: [M] [T] [W] [T] [F] [S] [S]
        14   15   16   17   18   19   20
  • Row 1 (top row): "Sessions" title left-aligned (black, 30px, weight 700, uses HEADER_TITLE_STYLE from tabBarConfig). Fire emoji + streak number right-aligned (amber/orange).
  • Row 2 (week row): 7 day columns spread edge-to-edge (justifyContent: space-between). Each column has a day-of-week label on top and a 38px date circle below with the calendar date number inside.
  • Week calculation: Computes Mon–Sun dates for the current week using new Date(). Days map to getDay() values for matching against weeklyWorkoutDays.
  • Safe area: Uses useSafeAreaInsets() for top padding. Container has white background and 16px horizontal padding.

Data

  • Fetches via apiService.getStreakData()
  • Refetches on screen focus via useFocusEffect
  • Returns null while loading (no layout shift)

Colors

Element Color Hex
Title text Black #000000
Fire emoji Native emoji β€”
Streak number Amber #F59E0B
Day label text Gray #9CA3AF
Date circle β€” completed Green #10B981
Date circle β€” today (not yet done) Amber #F59E0B
Date circle β€” default Light gray #E5E7EB
Date text β€” completed/today White #FFFFFF
Date text β€” default Dark gray #4B5563

Dimensions

  • Fire emoji: 24px font size
  • Streak number: 22px, weight 800
  • Day labels: 11px, weight 600
  • Date circles: 38Γ—38px (border radius 19)
  • Date text: 13px, weight 700
  • Container padding: 16px horizontal, 8px bottom, 8px row gap

Key Files

File Purpose
Okta-Mobile/components/navigation/tabBarConfig.ts Single source of truth for all colors, dimensions, spring configs, helper functions, and useTabBarBottomPadding() hook
Okta-Mobile/components/navigation/CustomTabBar.tsx Custom animated tab bar component (receives BottomTabBarProps)
Okta-Mobile/components/navigation/StreakHeaderWidget.tsx Compact streak display for the Dashboard header (patient only)
Okta-Mobile/app/(tabs)/_layout.tsx Wires up custom tab bar and header styling via Tabs navigator

Animation Spring Configs

Animation Damping Stiffness Used For
Pill slide 18 200 Translating pill between tab positions
Icon scale 12 250 Scaling active/inactive icons
Color transition 18 200 Interpolating pill background color

Dependencies

No custom dependencies β€” everything uses packages already in the project: - react-native-reanimated (~3.16.1) β€” all animations - expo-haptics (~14.0.1) β€” tap feedback - @expo/vector-icons (Ionicons) β€” tab icons - react-native-safe-area-context β€” bottom inset for floating bar positioning

Future Considerations

  • Profile screen: Has its own in-content purple banner (#667EEA) below the header that may look redundant with the new styled header. Could consider hiding the header on Profile or merging the banner with it.
  • Dark mode: Colors and backgrounds are currently hardcoded for light mode. A dark mode pass would need to update the white backgrounds and potentially adjust accent color brightness.
  • Content overlap: Handled via useTabBarBottomPadding() hook from tabBarConfig.ts. Each tab screen applies it to its scroll view's contentContainerStyle to ensure content clears the floating bar while still scrolling behind it.