# POWR App Styling Guide **Last Updated:** 2025-04-01 **Status:** Active **Related To:** Design System, Component Architecture, UI/UX, Cross-Platform Development ## Purpose This document outlines the styling principles, component usage patterns, and theming implementation for the POWR fitness app. Following these guidelines ensures a consistent look and feel across the application, facilitates cross-platform compatibility, and enhances the overall user experience. ## Cross-Platform Approach The POWR app is designed to run on both iOS and Android platforms, which present unique challenges for UI implementation. Our approach prioritizes: 1. **Platform Consistency**: Maintaining a consistent look and feel across platforms 2. **Platform Adaptation**: Respecting platform-specific UX patterns where appropriate 3. **Graceful Fallbacks**: Implementing fallbacks for features not available on all platforms 4. **Testing on Both Platforms**: All UI changes must be verified on both iOS and Android ## Theme System Architecture The POWR app uses a flexible theme system built with React Native, Tailwind CSS, and shadcn/ui components. The theming infrastructure supports both light and dark modes, with dynamic color adjustments for different UI states. ### Theme File Organization ``` lib/theme/ ├── index.ts - Main theme export ├── colors.ts - Color definitions ├── constants.ts - Theme constants ├── iconUtils.ts - Icon styling utilities └── useColorScheme.tsx - Theme mode selection hook ``` ### Theme Implementation Strategy The application uses: - Tailwind classes for general styling with `nativewind` - Specialized hooks for cross-platform compatibility (`useIconColor`, etc.) - `shadcn/ui` component library for consistent UI elements ## Color System All colors should be accessed through the theme system rather than using hardcoded values. Never use direct color codes in components. ### Color Imports ```typescript // Import theme utilities import { useTheme } from '@/lib/theme'; import { useIconColor } from '@/lib/theme/iconUtils'; import { FIXED_COLORS } from '@/lib/theme/colors'; ``` ### Color Variants The theme includes semantic color variants for different UI elements: - `primary` - Brand color, used for main interactive elements (purple) - `secondary` - Supporting UI elements - `muted` - Subdued elements, backgrounds, disabled states - `accent` - Highlights and accents - `destructive` - Error states, deletion actions (red) - `success` - Confirmation, completion states (green) - `warning` - Caution states (yellow/orange) ### Accessing Colors Always access colors through Tailwind classes: ```jsx // Good - uses theme system Hello World // Bad - hardcoded values that won't respond to theme changes Hello World ``` ## Icon Styling Icons must use the icon utility functions to ensure visibility across platforms. Different platforms may require different stroke widths, colors, and other properties. ### Icon Usage ```typescript import { useIconColor } from '@/lib/theme/iconUtils'; import { Play, Star, Trash2 } from 'lucide-react-native'; // Inside your functional component function MyComponent() { const { getIconProps, getIconColor } = useIconColor(); return ( {/* Primary action icon */} {/* Destructive action icon */} {/* Icon with conditional fill */} ); } ``` ### Icon Variants - `primary` - For main actions and interactive elements - `muted` - For secondary or less important actions - `destructive` - For delete/remove actions - `success` - For confirmation/complete actions - `warning` - For caution indicators ### Platform-Specific Icon Considerations - **Android**: - Icons often appear thinner and less visible on Android - Always use `strokeWidth={2}` or higher for better visibility on Android - Minimum recommended icon size is 24px for Android (vs. 20px for iOS) - Use the `getIconProps` function which handles these platform differences automatically - **iOS**: - Icons generally appear as expected with default stroke width - iOS has better support for gradients and complex icon styles ## Button Styling Use the standard `Button` component with appropriate variants to maintain a consistent look and feel. ### Button Variants ```jsx import { Button } from '@/components/ui/button'; import { Text } from '@/components/ui/text'; // Primary button // Destructive button // Outline button // Ghost button (minimal visual impact) // Link button ``` ### Button States Buttons handle the following states automatically through the theme system: - Default - Hover/active (handled differently on mobile and web) - Disabled - Loading ```jsx // Disabled button // Loading button ``` ### Platform-Specific Button Considerations - **Android**: - Android buttons may need additional padding to match iOS visual weight - Use `android:elevation` or equivalent shadow values for proper elevation on Android - Ripple effects require additional configuration to work properly - Consider using `TouchableNativeFeedback` for Android-specific feedback on buttons - **iOS**: - iOS buttons typically have more subtle feedback effects - Shadow properties work more predictably on iOS ## Header Component Use the `Header` component consistently across all screens for navigation and context. ### Header Configuration ```jsx import { Header } from '@/components/Header'; // Standard header with title
// Header with logo
// Header with custom right element
} /> // Header with back button
navigation.goBack()} /> ``` ### Platform-Specific Header Considerations - **Android**: - Android status bar customization requires `StatusBar` component with platform checks - Text in headers may render differently, requiring platform-specific adjustments - Back button styling differs between platforms - use the Header component's built-in options - Shadow effects need to be handled differently on Android (elevation vs shadowProps) - **iOS**: - iOS has native support for large titles and collapsible headers - Safe area insets are critical for proper header positioning on iOS - Status bar content color changes (dark/light) may need to be explicitly specified ## Text Styling Use the `Text` component with appropriate Tailwind classes for typography. This ensures the correct font styles across platforms. ### Text Hierarchy ```jsx import { Text } from '@/components/ui/text'; // Page title Page Title // Section heading Section Heading // Subsection heading Subsection Heading // Body text Regular body text for primary content. // Secondary text Secondary information or supporting text. // Small text / captions Caption text, timestamps, etc. ``` ### Platform-Specific Text Considerations - **Android**: - Font rendering is different on Android - text may appear smaller or thinner - Android requires explicit `fontFamily` specification for custom fonts - Line height calculations differ between platforms - may need adjustments - Some text styling properties like `letterSpacing` work differently on Android - Use `includeFontPadding: false` on Android to fix inconsistent text height - **iOS**: - Dynamic Type (iOS accessibility feature) should be supported - Certain text styles like small caps require different implementations - Font weights map differently between platforms (400 on iOS may not look the same as 400 on Android) ## Card Components Use the Card component family for content blocks throughout the app. ### Basic Card ```jsx import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; Card Title Card content goes here. ``` ### Interactive Card For cards that function as buttons: ```jsx Interactive Card Tap to interact ``` ### Platform-Specific Card Considerations - **Android**: - Use `elevation` for shadows on Android instead of `shadow-*` classes - Border radius may render differently on older Android versions - Ripple effects for interactive cards need platform-specific configuration - Border styles may appear differently on Android - **iOS**: - Shadow properties work more predictably on iOS - Cards with dynamic height may need additional configuration for iOS ## Dialog/Alert Styling Center buttons in dialogs for better usability and maintain consistent styling for these components. ### Alert Dialog Example ```jsx import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; Confirm Action Are you sure you want to continue? This action cannot be undone. Cancel Confirm ``` ### Platform-Specific Dialog Considerations - **Android**: - Android dialogs traditionally have buttons aligned to the right - Back button behavior needs special handling on Android dialogs - Touch outside to dismiss works differently on Android - Dialog animations differ between platforms - Material Design guidelines suggest different spacing and typography than iOS - **iOS**: - iOS dialogs typically have vertically stacked buttons - Safe area insets must be respected on full-screen iOS sheets - iOS has specific swipe gestures for sheet dismissal ## Form Elements Style form elements consistently for a coherent user experience. ### Form Field Examples ```jsx import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; // Text input with label {error && ( {error} )} // Select input with label ``` ### Platform-Specific Form Element Considerations - **Android**: - Input fields may need additional padding or height adjustments - Text field focus appearance differs significantly (Material Design vs. iOS) - Android requires explicit configuration for soft keyboard behavior - Date/time pickers have completely different UIs between platforms - Dropdown selects appear and behave differently on Android - **iOS**: - Form elements typically have a lighter visual style - iOS has specific picker components that are different from Android - Keyboard accessories are common on iOS but less so on Android - Text selection handles and behavior differ between platforms ## Common Cross-Platform Issues and Solutions ### Shadow Implementation **Issue**: Shadow styling works differently between iOS and Android. **Solution**: ```jsx // Cross-platform shadow solution Content with consistent shadow across platforms ``` ### Icon Rendering **Issue**: Icons appear properly on iOS but are barely visible on Android. **Solution**: ```jsx // Always use the icon utility import { useIconColor } from '@/lib/theme/iconUtils'; function MyComponent() { const { getIconProps } = useIconColor(); return ( ); } ``` ### Text Alignment **Issue**: Text alignment and truncation behaves differently across platforms. **Solution**: ```jsx // Text alignment helper component function AlignedText({ children, ...props }) { return ( {children} ); } ``` ### Touchable Feedback **Issue**: Touch feedback effects differ between platforms. **Solution**: ```jsx // Platform-specific touchable function AppTouchable({ children, onPress, ...props }) { if (Platform.OS === 'android') { return ( {children} ); } return ( {children} ); } ``` ### Keyboard Handling **Issue**: Keyboard behavior and avoidance differs between platforms. **Solution**: ```jsx // Keyboard handling helper {/* Form content */} ``` ## Platform-Specific Component Extensions For cases where significant platform differences exist, create platform-specific component extensions: ### Example: Platform-Specific DatePicker ```jsx // DatePickerWrapper.jsx import { DatePicker } from './DatePicker.ios'; import { DatePicker } from './DatePicker.android'; export const DatePickerWrapper = (props) => { const Component = Platform.select({ ios: DatePickerIOS, android: DatePickerAndroid, }); return ; }; ``` ## Best Practices for Cross-Platform Development 1. **Always test on both platforms** before considering a feature complete 2. **Use platform detection judiciously** - prefer cross-platform solutions where possible 3. **Create abstraction layers** for significantly different platform components 4. **Leverage UI component libraries** that handle cross-platform differences (like UI Kitten, React Native Paper) 5. **Document platform-specific quirks** that you encounter for future reference 6. **Create utility functions** for common platform-specific adjustments 7. **Use feature detection** instead of platform detection when possible 8. **Consider native device capabilities** like haptic feedback that may not exist on all devices ## Best Practices for POWR App Styling 1. **Never use hardcoded colors** - Always use theme variables through Tailwind classes 2. **Always use `getIconProps` for icons** - Ensures visibility on both iOS and Android 3. **Use semantic variants** - Choose button and icon variants based on their purpose 4. **Maintain consistent spacing** - Use Tailwind spacing classes (p-4, m-2, etc.) 5. **Test both platforms** - Verify UI rendering on both iOS and Android 6. **Use platform-specific overrides** when necessary 7. **Document platform-specific behavior** in component comments ## Troubleshooting Common Issues ### Icons Not Visible on Android Problem: Icons don't appear or are difficult to see on Android devices. Solution: - Ensure you're using `getIconProps()` instead of direct styling - Add `strokeWidth={2}` to increase visibility - Verify that icon size is appropriate (min 24px recommended for Android) - Check that the icon color has sufficient contrast with the background ### Inconsistent Colors Problem: Colors appear inconsistent between components or platforms. Solution: - Verify you're using Tailwind classes (text-primary vs #8B5CF6) - Check that the correct variant is being used for the component - Ensure components are properly wrapped with theme provider - Examine component hierarchy for style inheritance issues ### Text Truncation Issues Problem: Text doesn't truncate properly or layout breaks with long content. Solution: - Add `numberOfLines={1}` for single-line truncation - Use `ellipsizeMode="tail"` for text truncation - Wrap Text components with a fixed-width container - Consider using a more robust solution for responsive text - Apply platform-specific text style adjustments ### Shadow and Elevation Problem: Shadows appear on iOS but not on Android, or look inconsistent. Solution: - Use platform-specific shadow implementation (see example above) - For Android, use `elevation` property - For iOS, use `shadowColor`, `shadowOffset`, `shadowOpacity`, and `shadowRadius` - Test shadow values on different Android versions ### Keyboard Issues Problem: Keyboard covers input fields or doesn't dismiss properly. Solution: - Use KeyboardAvoidingView with platform-specific behavior - Implement Keyboard.dismiss on background taps - Add ScrollView for forms to ensure all fields are accessible - Consider using a keyboard manager library for complex forms ## Related Documentation - [Coding Style Guide](../../guides/coding_style.md) - General coding patterns and practices - [Component Architecture](../../architecture/index.md) - How components are organized - [Accessibility Guidelines](../../guides/accessibility.md) - Making the app accessible to all users