- Overhaul theme system with Svrnty design and WCAG AAA compliance - Remove android, macos, and web platform files (iOS-only focus) - Update components with improved dark mode map and UI refinements - Enhance settings page with additional configuration options - Add theme system documentation in lib/theme/README.md - Update CLAUDE.md with comprehensive theme guidelines Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
321 lines
9.2 KiB
Markdown
321 lines
9.2 KiB
Markdown
# Svrnty Theme System - Quick Reference
|
|
|
|
## Standard Color Access Pattern
|
|
|
|
**ALWAYS use:**
|
|
```dart
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
|
|
// Primary UI
|
|
colorScheme.primary // Primary brand color (Crimson Red)
|
|
colorScheme.onPrimary // Text on primary (white/black depending on theme)
|
|
|
|
// Secondary UI
|
|
colorScheme.secondary // Secondary brand color (Slate Blue)
|
|
colorScheme.onSecondary // Text on secondary
|
|
|
|
// Text
|
|
colorScheme.onSurface // Primary text
|
|
colorScheme.onSurfaceVariant // Secondary text
|
|
|
|
// Backgrounds
|
|
colorScheme.surface // Page background
|
|
colorScheme.surfaceContainer // Card background
|
|
|
|
// Status (specialized)
|
|
StatusColorScheme.getStatusColor(status)
|
|
StatusColorScheme.getStatusColorFromTheme(status, colorScheme) // Theme-aware (preferred)
|
|
```
|
|
|
|
**NEVER use:**
|
|
```dart
|
|
Colors.white // FORBIDDEN - use colorScheme.onPrimary or onSurface
|
|
Colors.black // FORBIDDEN - use colorScheme.onSurface or scrim
|
|
Color(0xFFXXXXXX) // FORBIDDEN in components (except in theme files)
|
|
SvrntyColors.crimsonRed // FORBIDDEN - use colorScheme.primary instead
|
|
```
|
|
|
|
## Theme Variants
|
|
|
|
The app provides **2 theme variants**:
|
|
|
|
### Light Theme
|
|
- **Background:** White (#FCFCFC)
|
|
- **Primary:** Crimson Red (#C91F37) - high contrast variant
|
|
- **Secondary:** Dark Slate (#2D3843) - high contrast variant
|
|
- **Text:** Very dark gray (#1A1C1E) - 16.5:1 contrast (WCAG AAA)
|
|
- **Secondary Text:** Dark gray (#3E4A56) - 7:1 contrast (WCAG AAA)
|
|
|
|
### Dark Theme (Forest Green)
|
|
- **Background:** Dark Forest Green (#0C1410) - unique branding
|
|
- **Primary:** Bright Crimson (#FF5A6D) - optimized for dark backgrounds
|
|
- **Secondary:** Light Slate Gray (#A5B6C8)
|
|
- **Text:** Pure white (#FFFFFF) - 18.2:1 contrast (WCAG AAA)
|
|
- **Secondary Text:** Light gray-green (#E0E8E4) - 14.1:1 contrast (WCAG AAA)
|
|
|
|
All text colors are WCAG AAA compliant (minimum 7:1 contrast for normal text, 4.5:1 for large text).
|
|
|
|
## Color Access Examples
|
|
|
|
### Good Examples
|
|
|
|
```dart
|
|
// Text on colored background (e.g., status badge, avatar)
|
|
Container(
|
|
color: Theme.of(context).colorScheme.primary,
|
|
child: Text(
|
|
'Label',
|
|
style: TextStyle(color: Theme.of(context).colorScheme.onPrimary),
|
|
),
|
|
)
|
|
|
|
// Primary text on page background
|
|
Text(
|
|
'Hello',
|
|
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
|
|
)
|
|
|
|
// Secondary/muted text
|
|
Text(
|
|
'Description',
|
|
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
|
)
|
|
|
|
// Shadow color
|
|
BoxShadow(
|
|
color: Theme.of(context).colorScheme.scrim.withValues(alpha: 0.2),
|
|
)
|
|
```
|
|
|
|
### Bad Examples (DON'T DO THIS)
|
|
|
|
```dart
|
|
// WRONG - hardcoded white
|
|
Text('Label', style: TextStyle(color: Colors.white))
|
|
|
|
// WRONG - hardcoded black
|
|
BoxShadow(color: Colors.black.withValues(alpha: 0.2))
|
|
|
|
// WRONG - hardcoded color value
|
|
Container(color: Color(0xFFFF9800))
|
|
|
|
// WRONG - using SvrntyColors directly for UI elements
|
|
Container(color: SvrntyColors.crimsonRed) // Use colorScheme.primary instead
|
|
```
|
|
|
|
## Status Colors
|
|
|
|
For delivery status indicators, use the `StatusColorScheme` utility:
|
|
|
|
```dart
|
|
import '../theme/status_colors.dart';
|
|
|
|
// Get status color (hardcoded, consistent across themes)
|
|
final color = StatusColorScheme.getStatusColor('completed');
|
|
|
|
// Get status color from theme (preferred - adapts to theme)
|
|
final themeColor = StatusColorScheme.getStatusColorFromTheme(
|
|
'completed',
|
|
Theme.of(context).colorScheme,
|
|
);
|
|
|
|
// Get background and text colors
|
|
final bgColor = StatusColorScheme.getStatusBackground('completed');
|
|
final textColor = StatusColorScheme.getStatusText('completed');
|
|
|
|
// Use pre-built widgets
|
|
StatusBadgeWidget(status: 'completed')
|
|
StatusAccentBar(status: 'in_transit')
|
|
```
|
|
|
|
**Supported Status Values:**
|
|
- `pending` - Amber (attention needed)
|
|
- `in_transit`, `in_progress`, `processing` - Slate blue (active)
|
|
- `completed`, `delivered`, `done` - Green (success)
|
|
- `failed`, `error` - Red (problem)
|
|
- `cancelled`, `skipped`, `rejected` - Gray (inactive)
|
|
- `on_hold`, `paused`, `waiting` - Slate (informational)
|
|
|
|
## Progress Gradient Colors
|
|
|
|
For progress indicators (e.g., route completion):
|
|
|
|
```dart
|
|
import '../theme/color_system.dart';
|
|
|
|
// Progress colors (0-100%)
|
|
SvrntyColors.progressLow // Orange - 0-40%
|
|
SvrntyColors.progressMedium // Amber - 40-70%
|
|
SvrntyColors.progressHigh // Green - 70-100%
|
|
|
|
// Example: color interpolation
|
|
Color progressColor = Color.lerp(
|
|
SvrntyColors.progressLow,
|
|
SvrntyColors.progressMedium,
|
|
progressPercent,
|
|
)!;
|
|
```
|
|
|
|
## Modifying Colors
|
|
|
|
### Changing Brand Colors
|
|
|
|
Edit the ColorScheme definitions in `/lib/theme.dart`:
|
|
|
|
```dart
|
|
// Light theme
|
|
static ColorScheme lightScheme() {
|
|
return const ColorScheme(
|
|
brightness: Brightness.light,
|
|
primary: Color(0xffC91F37), // Change this for new primary color
|
|
secondary: Color(0xff2D3843), // Change this for new secondary color
|
|
// ... rest of colors
|
|
);
|
|
}
|
|
|
|
// Dark theme
|
|
static ColorScheme darkScheme() {
|
|
return const ColorScheme(
|
|
brightness: Brightness.dark,
|
|
primary: Color(0xffFF5A6D), // Change this for new primary color
|
|
secondary: Color(0xffA5B6C8), // Change this for new secondary color
|
|
// ... rest of colors
|
|
);
|
|
}
|
|
```
|
|
|
|
### Adding New Semantic Colors
|
|
|
|
Add to `/lib/theme/color_system.dart`:
|
|
|
|
```dart
|
|
class SvrntyColors {
|
|
// ... existing colors
|
|
|
|
/// New semantic color
|
|
static const Color myNewColor = Color(0xFFXXXXXX);
|
|
}
|
|
```
|
|
|
|
Then use it in components:
|
|
|
|
```dart
|
|
Container(color: SvrntyColors.myNewColor)
|
|
```
|
|
|
|
### Changing Status Colors
|
|
|
|
Edit `/lib/theme/status_colors.dart`:
|
|
|
|
```dart
|
|
class StatusColorScheme {
|
|
static const Color completed = Color(0xFF22C55E); // Change this
|
|
static const Color completedBackground = Color(0xFFD1FAE5); // Change this
|
|
static const Color completedText = Color(0xFF065F46); // Change this
|
|
// ... rest of colors
|
|
}
|
|
```
|
|
|
|
## Text Theme
|
|
|
|
The app uses **Montserrat** font family for all text with explicit color assignments.
|
|
|
|
**Font Weights:**
|
|
- 300 (Light) - Unused in current design
|
|
- 400 (Regular) - Body text
|
|
- 500 (Medium) - Labels, buttons
|
|
- 600 (SemiBold) - Headings, titles
|
|
- 700 (Bold) - Display text, emphasis
|
|
|
|
**Text Styles:**
|
|
```dart
|
|
Theme.of(context).textTheme.displayLarge // 57px, bold, onSurface
|
|
Theme.of(context).textTheme.headlineMedium // 28px, semibold, onSurface
|
|
Theme.of(context).textTheme.titleLarge // 22px, semibold, onSurface
|
|
Theme.of(context).textTheme.bodyMedium // 14px, regular, onSurface
|
|
Theme.of(context).textTheme.labelSmall // 11px, medium, onSurfaceVariant
|
|
```
|
|
|
|
All text styles automatically adapt to light/dark themes with proper contrast.
|
|
|
|
## Accessibility
|
|
|
|
All color combinations meet **WCAG AAA** standards (7:1 contrast for normal text, 4.5:1 for large text):
|
|
|
|
**Light Theme:**
|
|
- Primary text on background: 16.5:1 (WCAG AAA)
|
|
- Secondary text on background: 7:1 (WCAG AAA)
|
|
- Text on primary color: 6.2:1 (WCAG AA Large)
|
|
|
|
**Dark Theme:**
|
|
- Primary text on background: 18.2:1 (WCAG AAA)
|
|
- Secondary text on background: 14.1:1 (WCAG AAA)
|
|
- Text on primary color: 11.8:1 (WCAG AAA)
|
|
|
|
## Component Themes
|
|
|
|
Component-specific theme configurations are in `/lib/theme/component_themes.dart`:
|
|
|
|
- CardTheme - Elevated cards with subtle shadows
|
|
- AppBarTheme - Navigation bars
|
|
- ButtonTheme - Filled, outlined, elevated buttons
|
|
- InputDecorationTheme - Text fields
|
|
- SnackBarTheme - Toast messages
|
|
- DialogTheme - Modal dialogs
|
|
- BottomNavigationBarTheme - Bottom navigation
|
|
- ChipTheme - Status chips
|
|
- ProgressIndicatorTheme - Loading indicators
|
|
- FloatingActionButtonTheme - FAB
|
|
- SliderTheme - Range inputs
|
|
|
|
All component themes use `ColorScheme` properties for consistency.
|
|
|
|
## Migration Guide
|
|
|
|
If you need to migrate old hardcoded colors to the new theme system:
|
|
|
|
### Step 1: Find Hardcoded Colors
|
|
```bash
|
|
# Search for hardcoded colors in your components
|
|
grep -r "Colors\.white\|Colors\.black\|Color(0x" lib/components/ lib/pages/
|
|
```
|
|
|
|
### Step 2: Replace with Theme Colors
|
|
|
|
| Old Pattern | New Pattern |
|
|
|------------|-------------|
|
|
| `Colors.white` on colored bg | `colorScheme.onPrimary` |
|
|
| `Colors.white` on page | `colorScheme.surface` |
|
|
| `Colors.black` for text | `colorScheme.onSurface` |
|
|
| `Colors.black` for shadow | `colorScheme.scrim` |
|
|
| `Color(0xFFXXXXXX)` | Use `colorScheme` or `SvrntyColors` |
|
|
|
|
### Step 3: Test Both Themes
|
|
- Run app in light mode, verify all text is visible
|
|
- Run app in dark mode, verify all text is visible
|
|
- Check color contrast with browser dev tools
|
|
|
|
## Troubleshooting
|
|
|
|
**Text not visible in dark mode:**
|
|
- Ensure you're using `colorScheme.onSurface` or `onSurfaceVariant` for text colors
|
|
- Don't use hardcoded `Colors.black` or dark colors for text
|
|
- Check that TextTheme is properly applied with `_buildTextTheme()`
|
|
|
|
**Colors not updating when switching themes:**
|
|
- Use `Theme.of(context).colorScheme` instead of `SvrntyColors` constants
|
|
- Ensure widget rebuilds when theme changes (use `ConsumerWidget` for Riverpod)
|
|
- Avoid caching ColorScheme outside of build method
|
|
|
|
**Wrong colors in components:**
|
|
- Verify component uses `colorScheme` parameter correctly
|
|
- Check that custom theme is applied in MaterialApp
|
|
- Use `theme()` method to generate ThemeData
|
|
|
|
## Resources
|
|
|
|
- **Material Design 3:** https://m3.material.io/
|
|
- **WCAG Contrast Checker:** https://webaim.org/resources/contrastchecker/
|
|
- **Flutter Theme Guide:** https://docs.flutter.dev/cookbook/design/themes
|
|
- **ColorScheme Docs:** https://api.flutter.dev/flutter/material/ColorScheme-class.html
|