Chromia UI provides built-in support for light and dark themes with automatic color adjustments that ensure proper contrast and readability.
Quick Start
The simplest way to implement light and dark themes is using the built-in factory constructors:
import 'package:chromia_ui/chromia_ui.dart' ;
// Light theme
final lightTheme = ChromiaThemeData . light ();
// Dark theme
final darkTheme = ChromiaThemeData . dark ();
Theme Brightness
Every ChromiaThemeData has a brightness property that indicates whether it’s a light or dark theme:
final theme = ChromiaThemeData . light ();
print (theme.brightness); // Brightness.light
print (theme.isLight); // true
print (theme.isDark); // false
Implementing Theme Toggle
Here’s a complete example showing how to implement a theme toggle:
Stateful Approach
Provider Pattern
import 'package:flutter/material.dart' ;
import 'package:chromia_ui/chromia_ui.dart' ;
class MyApp extends StatefulWidget {
@override
State < MyApp > createState () => _MyAppState ();
}
class _MyAppState extends State < MyApp > {
bool isDarkMode = false ;
void toggleTheme () {
setState (() {
isDarkMode = ! isDarkMode;
});
}
@override
Widget build ( BuildContext context) {
final chromiaTheme = isDarkMode
? ChromiaThemeData . dark ()
: ChromiaThemeData . light ();
return ChromiaTheme (
data : chromiaTheme,
child : MaterialApp (
theme : chromiaTheme. toMaterialTheme (),
home : HomePage (onToggleTheme : toggleTheme),
),
);
}
}
System Theme Detection
You can detect the system’s preferred theme and respect it:
import 'package:flutter/material.dart' ;
import 'package:chromia_ui/chromia_ui.dart' ;
class MyApp extends StatelessWidget {
@override
Widget build ( BuildContext context) {
// Get system brightness
final brightness = MediaQuery . platformBrightnessOf (context);
final isDarkMode = brightness == Brightness .dark;
final chromiaTheme = isDarkMode
? ChromiaThemeData . dark ()
: ChromiaThemeData . light ();
return ChromiaTheme (
data : chromiaTheme,
child : MaterialApp (
theme : chromiaTheme. toMaterialTheme (),
home : HomePage (),
),
);
}
}
Light vs Dark Color Differences
Chromia UI automatically adjusts colors for optimal contrast in light and dark modes:
Primary Colors
primary : ColorTokens .primary600 // Darker shade for light backgrounds
primaryHover : ColorTokens .primary800
primaryPressed : ColorTokens .primary900
onPrimary : ColorTokens .neutral0 // White text on primary
primary : ColorTokens .primary400 // Lighter shade for dark backgrounds
primaryHover : ColorTokens .primary300
primaryPressed : ColorTokens .primary200
onPrimary : ColorTokens .neutral900 // Dark text on primary
Surface Colors
surface : ColorTokens .neutral0 // White
background : ColorTokens .neutral50 // Off-white
onSurface : ColorTokens .neutral900 // Dark text
surface : ColorTokens .neutral900 // Dark gray
background : ColorTokens .neutral1000 // Near black
onSurface : ColorTokens .neutral100 // Light text
Text Colors
textPrimary : ColorTokens .neutral900 // Dark text
textSecondary : ColorTokens .neutral700 // Medium gray
textTertiary : ColorTokens .neutral500 // Light gray
textPrimary : ColorTokens .neutral100 // Light text
textSecondary : ColorTokens .neutral300 // Medium gray
textTertiary : ColorTokens .neutral500 // Darker gray
Theme-Aware Widgets
Use context extensions to build widgets that automatically adapt to the current theme:
class ThemedCard extends StatelessWidget {
final String title;
final String subtitle;
const ThemedCard ({
required this .title,
required this .subtitle,
});
@override
Widget build ( BuildContext context) {
final colors = context.chromiaColors;
final typography = context.chromiaTypography;
final spacing = context.chromiaSpacing;
final radius = context.chromiaRadius;
final shadows = context.chromiaShadows;
return Container (
padding : spacing.paddingL,
decoration : BoxDecoration (
color : colors.surface,
borderRadius : radius.radiusM,
border : Border . all (color : colors.border),
boxShadow : shadows.s,
),
child : Column (
crossAxisAlignment : CrossAxisAlignment .start,
children : [
Text (
title,
style : typography.titleMedium. copyWith (
color : colors.textPrimary,
),
),
spacing.gapVS,
Text (
subtitle,
style : typography.bodyMedium. copyWith (
color : colors.textSecondary,
),
),
],
),
);
}
}
Conditional Rendering
Sometimes you need to render different content based on the theme:
class ThemeAwareIcon extends StatelessWidget {
@override
Widget build ( BuildContext context) {
final isDark = context.isDarkTheme;
return Icon (
isDark ? Icons .dark_mode : Icons .light_mode,
color : context.chromiaColors.textPrimary,
);
}
}
Custom Colors for Dark Mode
You can create custom color schemes that work differently in light and dark modes:
ChromiaThemeData createCustomTheme ( bool isDark) {
final baseTheme = isDark
? ChromiaThemeData . dark ()
: ChromiaThemeData . light ();
return baseTheme. copyWith (
colors : baseTheme.colors. copyWith (
primary : isDark
? Color ( 0xFF90CAF9 ) // Lighter blue for dark mode
: Color ( 0xFF1976D2 ), // Darker blue for light mode
),
);
}
Shadows in Dark Mode
Chromia UI provides different shadow configurations for light and dark themes:
final lightShadows = ChromiaShadows . light ();
final darkShadows = ChromiaShadows . dark ();
// Dark mode shadows are slightly more prominent
// to provide better depth perception
In dark mode, shadows use a slightly higher opacity to ensure they remain visible against dark backgrounds.
Persisting Theme Preference
To persist the user’s theme choice, use shared preferences or a similar storage solution:
import 'package:shared_preferences/shared_preferences.dart' ;
class ThemePreferences {
static const String _keyIsDarkMode = 'isDarkMode' ;
static Future < bool > getThemePreference () async {
final prefs = await SharedPreferences . getInstance ();
return prefs. getBool (_keyIsDarkMode) ?? false ;
}
static Future < void > setThemePreference ( bool isDarkMode) async {
final prefs = await SharedPreferences . getInstance ();
await prefs. setBool (_keyIsDarkMode, isDarkMode);
}
}
class ThemeNotifier extends ChangeNotifier {
bool _isDarkMode = false ;
ThemeNotifier () {
_loadThemePreference ();
}
Future < void > _loadThemePreference () async {
_isDarkMode = await ThemePreferences . getThemePreference ();
notifyListeners ();
}
bool get isDarkMode => _isDarkMode;
Future < void > toggleTheme () async {
_isDarkMode = ! _isDarkMode;
await ThemePreferences . setThemePreference (_isDarkMode);
notifyListeners ();
}
}
Best Practices
Always use semantic colors
Use semantic color names like colors.textPrimary instead of hardcoded colors. This ensures your UI adapts correctly to theme changes. // Good ✓
Text ( 'Hello' , style : TextStyle (color : context.chromiaColors.textPrimary));
// Bad ✗
Text ( 'Hello' , style : TextStyle (color : Colors .black));
Always test your UI in both light and dark themes to ensure proper contrast and readability.
Respect system preferences
Consider respecting the system theme by default, and provide users with the option to override it.
Prefer context extensions (context.chromiaColors) over ChromiaTheme.of(context).colors for cleaner code.
Next Steps
Custom Themes Create fully custom themes
Design Tokens Explore available color tokens