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
headercomponent (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 toheaderShown: false. - Messages: Black title. Has a "+" button in
headerRight(Ioniconsadd, size 28, colored viagetTabColor("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 (top row): "Sessions" title left-aligned (black, 30px, weight 700, uses
HEADER_TITLE_STYLEfromtabBarConfig). 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 togetDay()values for matching againstweeklyWorkoutDays. - 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
nullwhile 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 fromtabBarConfig.ts. Each tab screen applies it to its scroll view'scontentContainerStyleto ensure content clears the floating bar while still scrolling behind it.