Mobile App Style Guide
Comprehensive design reference for the TrainerDay mobile app. React Native 0.80, Styled Components, dark theme only. Designed for athletes who are sweating, gasping, and need to read numbers from a meter away.
Design Principles
The core ideas that shape every screen, component, and interaction
Friendly, Not Corporate
Rounded corners everywhere. Pill-shaped buttons. Warm blues. No sharp edges, no cold grays. TrainerDay feels like a helpful training buddy, not enterprise software. We want users to smile when they open the app.
Simple and Open
Generous whitespace. Clean hierarchy. One thing at a time. If a screen feels cluttered, remove something. If a user needs to think about how to use it, simplify it. The best UI is the one you don't notice.
Readable While Suffering
The workout screen is used while sweating, gasping, eyes blurring. Target numbers must be 64px+ bold, readable from 1 meter away. Max 3-4 info pieces visible. High contrast, no subtlety. Design for the hardest moment of the ride.
One System, Two Themes
Web and mobile share the same design language -- same primary blue, same font, same periodization colors, same friendly personality. Only the surface colors differ: light for web, dark for mobile. One brand, two contexts.
Color Tokens
All colors from designTokens.colors -- never hardcode hex values
Surfaces & Backgrounds
The dark layered surface system. Cards sit on backgrounds, modals sit on cards.
Text Color Hierarchy
Four levels of text prominence for creating visual hierarchy
Training Zones
Zwift-based zone palette shared across all platforms. Source: @trainerday/cycling-converter
Periodization Colors
Training plan phase indicators -- identical across web and mobile
#02c9af
Build #ffba4a
Peak #f86f2b
Event #1a9af6
| Component | Role | Value |
|---|---|---|
| Tab Bar | Focused | #2563eb |
| Tab Bar | Muted | #6E7085 |
| Tab Bar | Continue (blinks) | #48B833 |
| Switcher | Thumb On | #2C68DE |
| Switcher | Thumb Off | #86878B |
| Switcher | Track | #273551 |
| Chart | Workout Bar | #2C68DE (brandBlueLight) |
| Chart | Passed Overlay | #273551 |
| Chart | Progress Line | #1D75E7 |
| Chart | FTP Line | #e9a30050 (dashed) |
| Slider | Thumb | #2C68DE |
| Slider | Min Track | #3E5C97 |
| Slider | Max Track | #86878B |
| Indicator | Warning | #C40002 |
| Indicator | Out of Range | #C40002 |
| Toolbar | Button BG | #161820 |
| Toolbar | Icon Color | #CACACA |
Typography
DM Sans everywhere -- five weights available as separate font files in React Native
fontFamily (e.g. DMSans-Bold), not fontWeight.
Use TDText type="bold" to set the correct family.
Responsive Font Sizing
Workout indicator sizes scale with screen size. Source: src/services/css/font.ts
| Element | Default (xSmall) | Medium | Large | XLarge |
|---|---|---|---|---|
| Main Target | 64px / Bold | 68px | 72px | 76px |
| Main Target (second) | 64px / Medium | 68px | 72px | 88px |
| Main Unit | 16px | 28px | 28px | 28px |
| Device Value | 44px / Medium | 44px | 44px | 80px |
| Device Unit | 12px | 12px | 12px | 24px |
| Time | 40px / Medium | 44px | 44px | 80px |
| Start Button | 16px / Medium | 28px | 28px | 24px |
@/utils/screenInfo.
TDButton
Primary action button -- pill shape, 4 style variants, 2 sizes, optional icon. Always use this, never raw TouchableOpacity.
TDText
Base text component -- type prop controls font weight via font family. Never use raw Text.
TDTextInput
Floating label input -- used on auth screens. Pill-shaped, 56px height, animated label.
Switcher
Toggle switch -- native Switch component with custom TrainerDay colors
WorkoutCard
Two display modes: card style (Today tab) and list style (Workouts tab)
Zone Badges & Periodization Chips
Training zone indicators used throughout the app for badges, charts, and progress bars
Workout Indicators
Live data displays during active workout -- time, power, HR, cadence, target
Workout Controls
Action buttons during active workout -- 56x56px dark squares with icon
TabBar
Bottom tab navigation -- focused, muted, and blinking continue states
Settings Row
Two variants: navigation row (56px, with chevron) and switcher row (71px, with toggle)
TDPopup / Bottom Sheet
Dark surface popup overlay with close button, title, content, and action button
Active Workout Screen
Full vertical stack layout of the WorkoutPlayer screen
Status bar -- 44px
Header -- 50px
Time -- 62px
Device indicators -- 62px
Main target -- 84px
Controls row -- 56px
Chart area -- flex-grow (fills remaining)
Bottom toolbar -- 48px
Safe area -- 34px
Key rules:
Target power: 64px bold, most dominant element
Device values: 44px medium, secondary
Control buttons: 56x56px, bg #20232F, radius 5px
Chart: blue bars (#2C68DE), passed bars (#273551)
FTP dashed line, progress line (#1D75E7)
Bottom toolbar: bg #20232F, button bg #161820
Workout Screen Rules
Non-negotiable constraints for the active workout experience
- Critical numbers (power target, current power) -- must be LARGE, readable from 1 meter away (64-96px, weight 700)
- Touch targets -- minimum 56px height, bigger is better
- Maximum 3-4 pieces of information visible at once
- No small text for anything that matters during a workout
- High contrast -- no subtle color differences
- Current interval / what to do RIGHT NOW is always the most dominant element
- Next interval visible but clearly secondary
Golden Rules
The non-negotiable constraints
- Use
designTokens.colorsfor all colors - Use
TDButtonfor buttons,TDTextfor text - Pill-shaped buttons (radius 9999px)
- DM Sans everywhere, no exceptions
- 64px+ bold for workout target numbers
- 56px minimum touch targets on workout screens
- One primary blue button per screen
- Use
TDTextInputfor all text inputs - Use primitives from
@/services/css - Dark surface (#20232F) for cards on dark bg (#1A1C26)
- Use #83858B for unit labels and secondary text
- Settings rows: 56px height, 1px gap separator
- Hardcode hex colors -- always use tokens
- Use
StyleSheet.create-- styled-components only - Use raw
Text,TextInput, or custom buttons - Small text (<14px) for anything during a workout
- Sharp corners -- minimum 4px radius
- Cluttered workout screens -- max 3-4 info pieces
- System fonts or anything other than DM Sans
- Bright/light backgrounds -- dark theme only
- Multiple primary buttons competing for attention
- Subtle color differences for important state changes
- Small touch targets (<44px) anywhere
- Use legacy components (RedButton, TransparentButton, etc.)
Component → React Native Mapping
How each design catalog element maps to the actual React Native codebase
| Catalog Element | RN Component | Import Path | Key Props |
|---|---|---|---|
| Blue pill button | TDButton | @/components/TDButton | buttonStyle="main|secondary|warning-link|success", title, testID |
| X close icon | ButtonClose | @/components/ButtonClose | testID, onClose |
| Any text | TDText | @/components/TDText | type="light|regular|medium|semiBold|bold" |
| Floating label input | TDTextInput | @/components/TDTextInput | label, testID, isValid?, error?, secureTextEntry? |
| Checkbox | TDCheckbox | @/components/TDCheckbox | isChecked, onToggle, label?, testID |
| Toggle switch | Switcher | @/components/Switcher | value, onValueChange, testID |
| Dropdown | TDPicker | @/components/TDPicker | data, value, onChange, testID? |
| PRO badge | ProBadge | @/components/ProBadge | (no props) |
| Workout card | WorkoutCard | @/components/WorkoutChart/WorkoutCard | workout, width, ftp, segments, isCardStyle? |
| Navigation header | HeaderWithButtonGoBack | @/components/HeaderWithButtonGoBack | title, testID, onBack? |
| Modal frame | TDPopup | @/components/TDPopup | isVisible, onClose, title?, showCloseButton? |
| Settings nav row | SettingsButtonGoTo | @/screens/Settings/components/SettingsButtonGoTo | title, screen, testID |
| Settings toggle row | SettingsSwitcher | @/screens/Settings/components/SettingsSwitcher | title, description, value, onChange, testID |
| Offline message | NoInternetConnection | @/components/NoInternetConnection | (no props) |
| Guest sign-in card | GuestModeMessage | @/components/GuestModeMessage | (no props) |