Customization & Theming
Complete guide to customizing themes, colors, and design tokens in the Flutter boilerplate.
This guide covers how to customize the visual appearance of your app, including creating new themes, modifying colors, and understanding the design token system.
Architecture Overview
The theming system is built on design tokens - a set of semantic variables that define colors, typography, spacing, and other visual properties. Each theme is completely self-contained with its own color palette, making customization straightforward.
File Responsibilities
| File | Purpose |
|---|---|
design_tokens.dart | Barrel file that exports all theming components |
app_theme.dart | AppTheme enum and AppThemeRegistry |
app_text_styles.dart | Common text style definitions |
design_tokens_model.dart | DesignTokens class with all token properties |
*_theme.dart | Self-contained theme with *Colors class + tokens |
Built-in Themes
The boilerplate includes four pre-built themes, each with its own color palette:
| Theme | Description | Border Radius | Color Class |
|---|---|---|---|
| Modern | Clean, professional SaaS aesthetic | Medium (8px) | ModernColors |
| Retro | Bold, nostalgic vintage feel | Sharp (0px) | RetroColors |
| Cozy | Warm, inviting with soft corners | Large (18px) | CozyColors |
| Paper | Minimalist with pure scaffolds | Large (18px) | PaperColors |
Each theme supports both light and dark modes automatically.
Using Design Tokens
Access design tokens anywhere in your app using Riverpod:
import 'package:mobile/components/utils/design_tokens.dart';
import 'package:mobile/providers/theme_provider.dart';
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final tokens = ref.watch(designTokensProvider);
return Container(
padding: EdgeInsets.all(tokens.contentPadding),
decoration: BoxDecoration(
color: tokens.surface,
borderRadius: BorderRadius.circular(tokens.cardBorderRadius),
border: Border.all(color: tokens.border),
),
child: Text(
'Hello World',
style: tokens.bodyPrimary,
),
);
}
}Available Token Categories
// Primary & Secondary
tokens.primary // Brand primary color
tokens.onPrimary // Text/icons on primary
tokens.secondary // Brand secondary color
// Semantic Colors
tokens.success // Success/positive
tokens.error // Error/negative
tokens.warning // Warning/caution
tokens.info // Informational
// Surface Colors
tokens.background // Main background
tokens.surface // Cards/elevated surfaces
tokens.border // Borders/dividers
tokens.onSurface // Text/icons on surface
// Variants
tokens.surfaceVariant
tokens.surfaceDim
tokens.surfaceBright
tokens.muted // Disabled/placeholder// Spacing (gap between elements)
tokens.spacingXs // 4px (compact: 2px)
tokens.spacingSm // 8px (compact: 6px)
tokens.spacingMd // 16px (compact: 12px)
tokens.spacingLg // 24px (compact: 18px)
tokens.spacingXl // 32px (compact: 24px)
// Padding (internal element spacing)
tokens.paddingXs // 4px (compact: 2px)
tokens.paddingSm // 8px (compact: 6px)
tokens.paddingMd // 12px (compact: 8px)
tokens.paddingLg // 16px (compact: 12px)
// Layout
tokens.contentPadding // 20px (compact: 14px)
tokens.elementSpacing // 16px (compact: 12px)// Button Heights
tokens.buttonHeightSm // 36px (compact: 28px)
tokens.buttonHeightMd // 44px (compact: 36px)
tokens.buttonHeightLg // 52px (compact: 44px)
// Input & List
tokens.inputHeight // 48px (compact: 40px)
tokens.listItemHeight // 56px (compact: 44px)
// Icon Sizes
tokens.iconSizeSm // 16px (compact: 14px)
tokens.iconSizeMd // 20px (compact: 18px)
tokens.iconSizeLg // 24px (compact: 22px)
// Border Radii
tokens.buttonBorderRadius // Varies by theme
tokens.cardBorderRadius // Varies by theme
tokens.navbarBorderRadius // Varies by theme// Text Styles
tokens.bodyPrimary // Primary body text
tokens.bodySecondary // Secondary/muted text
tokens.disabledText // Disabled state textCreating a New Theme
Each theme is self-contained in a single file with its own color palette class. Follow these steps to add a custom theme.
Create Theme File
Create a new file in lib/components/utils/themes/definitions/:
/// Ocean theme definition.
///
/// A calming, aquatic theme inspired by the ocean.
library;
import 'package:flutter/material.dart';
import '../base/app_text_styles.dart';
import '../base/design_tokens_model.dart';
// =============================================================================
// OCEAN THEME COLORS
// =============================================================================
/// Ocean theme color palette
class OceanColors {
OceanColors._();
// ---------------------------------------------------------------------------
// PRIMARY & SECONDARY
// ---------------------------------------------------------------------------
static const Color primary = Color(0xFF0077B6);
static const Color primaryDark = Color(0xFF00B4D8);
static const Color secondary = Color(0xFF90E0EF);
static const Color secondaryDark = Color(0xFFCAF0F8);
// ---------------------------------------------------------------------------
// SEMANTIC COLORS
// ---------------------------------------------------------------------------
static const Color success = Color(0xFF10B981);
static const Color successDark = Color(0xFF6EE7B7);
static const Color error = Color(0xFFEF4444);
static const Color errorDark = Color(0xFFFCA5A5);
static const Color warning = Color(0xFFF59E0B);
static const Color warningDark = Color(0xFFFCD34D);
static const Color info = Color(0xFF3B82F6);
static const Color infoDark = Color(0xFF60A5FA);
// ---------------------------------------------------------------------------
// SURFACE & BACKGROUND
// ---------------------------------------------------------------------------
static const Color background = Color(0xFFFFFFFF);
static const Color backgroundDark = Color(0xFF03045E);
static const Color surface = Color(0xFFF0F9FF);
static const Color surfaceDark = Color(0xFF023E8A);
static const Color border = Color(0xFFADE8F4);
static const Color borderDark = Color(0xFF0077B6);
// ---------------------------------------------------------------------------
// ON-COLORS
// ---------------------------------------------------------------------------
static const Color onPrimary = Color(0xFFFFFFFF);
static const Color onPrimaryDark = Color(0xFF000000);
static const Color onSurface = Color(0xFF000000);
static const Color onSurfaceDark = Color(0xFFFFFFFF);
static const Color muted = Color(0xFF737373);
static const Color mutedDark = Color(0xFFa1a1a1);
// ---------------------------------------------------------------------------
// DARK MODE VARIANTS (copy these from existing themes)
// ---------------------------------------------------------------------------
static const Color surfaceVariantDark = Color(0xFF2A2A2A);
static const Color surfaceDimDark = Color(0xFF161616);
static const Color surfaceBrightDark = Color(0xFF252525);
static const Color outlineDark = Color(0xFF6B7280);
static const Color outlineVariantDark = Color(0xFF4B5563);
static const Color inverseSurfaceDark = Color(0xFFF5F5F5);
static const Color inverseOnSurfaceDark = Color(0xFF1F1F1F);
static const Color primaryContainerDark = Color(0xFF1A3A5C);
static const Color onPrimaryContainerDark = Color(0xFFD6E3FF);
static const Color secondaryContainerDark = Color(0xFF4A4458);
static const Color onSecondaryDark = Color(0xFF000000);
static const Color onSecondaryContainerDark = Color(0xFFE8DEF8);
static const Color errorContainerDark = Color(0xFF93000A);
static const Color onErrorDark = Color(0xFF690005);
static const Color onErrorContainerDark = Color(0xFFFFDAD6);
static const Color tertiaryDark = Color(0xFF818CF8);
static const Color tertiaryContainerDark = Color(0xFF3730A3);
static const Color onTertiaryDark = Color(0xFF1E1B4B);
static const Color onTertiaryContainerDark = Color(0xFFE0E7FF);
}
// =============================================================================
// OCEAN THEME TOKENS
// =============================================================================
/// Ocean theme (light mode).
const DesignTokens oceanLightTokens = DesignTokens(
primary: OceanColors.primary,
onPrimary: OceanColors.onPrimary,
secondary: OceanColors.secondary,
success: OceanColors.success,
error: OceanColors.error,
warning: OceanColors.warning,
info: OceanColors.info,
background: OceanColors.background,
surface: OceanColors.surface,
border: OceanColors.border,
onSurface: OceanColors.onSurface,
bodyPrimary: AppTextStyles.bodyPrimary,
bodySecondary: AppTextStyles.bodySecondary,
disabledText: AppTextStyles.mutedText,
buttonBorderRadius: 12.0,
cardBorderRadius: 16.0,
navbarBorderRadius: 24.0,
inversePrimary: OceanColors.primary,
);
/// Ocean theme (dark mode).
const DesignTokens oceanDarkTokens = DesignTokens(
primary: OceanColors.primaryDark,
onPrimary: OceanColors.onPrimaryDark,
secondary: OceanColors.secondaryDark,
success: OceanColors.successDark,
error: OceanColors.errorDark,
warning: OceanColors.warningDark,
info: OceanColors.infoDark,
background: OceanColors.backgroundDark,
surface: OceanColors.surfaceDark,
border: OceanColors.borderDark,
onSurface: OceanColors.onSurfaceDark,
surfaceVariant: OceanColors.surfaceVariantDark,
surfaceDim: OceanColors.surfaceDimDark,
surfaceBright: OceanColors.surfaceBrightDark,
outline: OceanColors.outlineDark,
outlineVariant: OceanColors.outlineVariantDark,
inverseSurface: OceanColors.inverseSurfaceDark,
inverseOnSurface: OceanColors.inverseOnSurfaceDark,
inversePrimary: OceanColors.primary,
primaryContainer: OceanColors.primaryContainerDark,
onPrimaryContainer: OceanColors.onPrimaryContainerDark,
secondaryContainer: OceanColors.secondaryContainerDark,
onSecondary: OceanColors.onSecondaryDark,
onSecondaryContainer: OceanColors.onSecondaryContainerDark,
errorContainer: OceanColors.errorContainerDark,
onError: OceanColors.onErrorDark,
onErrorContainer: OceanColors.onErrorContainerDark,
tertiary: OceanColors.tertiaryDark,
tertiaryContainer: OceanColors.tertiaryContainerDark,
onTertiary: OceanColors.onTertiaryDark,
onTertiaryContainer: OceanColors.onTertiaryContainerDark,
muted: OceanColors.mutedDark,
bodyPrimary: AppTextStyles.bodyPrimaryDark,
bodySecondary: AppTextStyles.bodySecondaryDark,
disabledText: AppTextStyles.disabledDark,
buttonBorderRadius: 12.0,
cardBorderRadius: 16.0,
navbarBorderRadius: 24.0,
);Register the Theme
Update app_theme.dart to include your new theme:
import 'definitions/ocean_theme.dart'; // Add this import
/// Supported application themes.
enum AppTheme {
modern,
retro,
cozy,
paper,
ocean, // Add your theme here
}
// In AppThemeRegistry:
static const Map<AppTheme, DesignTokens> _lightMap = {
AppTheme.modern: modernLightTokens,
AppTheme.retro: retroLightTokens,
AppTheme.cozy: cozyLightTokens,
AppTheme.paper: paperLightTokens,
AppTheme.ocean: oceanLightTokens, // Add here
};
static const Map<AppTheme, DesignTokens> _darkMap = {
AppTheme.modern: modernDarkTokens,
AppTheme.retro: retroDarkTokens,
AppTheme.cozy: cozyDarkTokens,
AppTheme.paper: paperDarkTokens,
AppTheme.ocean: oceanDarkTokens, // Add here
};Export the Theme
Add export to the barrel file:
// Theme definitions
export 'themes/definitions/cozy_theme.dart';
export 'themes/definitions/modern_theme.dart';
export 'themes/definitions/ocean_theme.dart'; // Add this
export 'themes/definitions/paper_theme.dart';
export 'themes/definitions/retro_theme.dart';Test Your Theme
Your new theme is now available! Users can select it in the Appearance settings, or you can set it programmatically:
// Set theme programmatically
ref.read(appThemeProvider.notifier).setTheme(AppTheme.ocean);
// Check current theme
final currentTheme = ref.watch(appThemeProvider);Customizing Existing Themes
Each theme is self-contained, making customization easy.
Changing Primary Colors
Modify the color constants in the theme's *Colors class:
class ModernColors {
// Before: Blue primary
static const Color primary = Color(0xFF7aaae6);
// After: Green primary
static const Color primary = Color(0xFF22C55E);
}Changing Border Radii
Modify the layout tokens in the theme definition:
const DesignTokens modernLightTokens = DesignTokens(
// ...other tokens...
// Change from 8.0 to 16.0 for more rounded corners
buttonBorderRadius: 16.0,
cardBorderRadius: 16.0,
navbarBorderRadius: 20.0,
);Changing Semantic Colors Per Theme
Since each theme defines its own colors, you can have different semantic colors:
class RetroColors {
// Retro theme uses different success color for vintage feel
static const Color success = Color(0xFF059669); // Slightly different green
static const Color error = Color(0xFFDC2626); // Different red shade
}Accent Color System
The app supports dynamic accent colors that override the primary color at runtime:
// Available accent colors
enum AccentColor {
emerald(Color(0xFF10B981), 'Emerald'),
blue(Color(0xFF7aaae6), 'Blue'),
indigo(Color(0xFF6366F1), 'Indigo'),
purple(Color(0xFF8B5CF6), 'Purple'),
pink(Color(0xFFEC4899), 'Pink'),
amber(Color(0xFFF59E0B), 'Amber');
}
// Set accent color
ref.read(accentColorProvider.notifier).setColor(AccentColor.purple);Adding More Accent Colors
Extend the AccentColor enum in theme_provider.dart:
enum AccentColor {
emerald(Color(0xFF10B981), 'Emerald'),
blue(Color(0xFF7aaae6), 'Blue'),
// Add your colors
teal(Color(0xFF14B8A6), 'Teal'),
rose(Color(0xFFF43F5E), 'Rose'),
cyan(Color(0xFF06B6D4), 'Cyan'),
}Compact Mode
The app supports a compact mode that reduces spacing for users who prefer denser UIs:
// Toggle compact mode
ref.read(compactModeProvider.notifier).setCompactMode(true);Compact mode automatically adjusts:
- Spacing tokens (reduced to ~70-80%)
- Padding tokens
- Component heights
- Border radii (proportionally scaled, except 0 and pill)
Best Practices
Do's
- ✅ Always use design tokens instead of hardcoded values
- ✅ Keep all theme colors in the theme's
*Colorsclass - ✅ Test themes in both light and dark modes
- ✅ Ensure sufficient contrast for accessibility
Don'ts
- ❌ Don't bypass the token system with inline colors
- ❌ Don't forget to add dark mode variants
- ❌ Don't use colors that fail accessibility contrast checks
- ❌ Don't reference colors from other theme files
Color Tools & Resources
- Coolors - Color palette generator
- Material Design Color Tool - M3 theme builder
- Contrast Checker - WCAG contrast validation
- Color Hunt - Curated color palettes