BowlerPlate

Customization & Theming

2/19/2026

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.

design_tokens.dart
app_theme.dart
app_text_styles.dart
design_tokens_model.dart
modern_theme.dart
retro_theme.dart
cozy_theme.dart
paper_theme.dart

File Responsibilities

FilePurpose
design_tokens.dartBarrel file that exports all theming components
app_theme.dartAppTheme enum and AppThemeRegistry
app_text_styles.dartCommon text style definitions
design_tokens_model.dartDesignTokens class with all token properties
*_theme.dartSelf-contained theme with *Colors class + tokens

Built-in Themes

The boilerplate includes four pre-built themes, each with its own color palette:

ThemeDescriptionBorder RadiusColor Class
ModernClean, professional SaaS aestheticMedium (8px)ModernColors
RetroBold, nostalgic vintage feelSharp (0px)RetroColors
CozyWarm, inviting with soft cornersLarge (18px)CozyColors
PaperMinimalist with pure scaffoldsLarge (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 text

Creating 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/:

lib/components/utils/themes/definitions/ocean_theme.dart
/// 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:

lib/components/utils/themes/app_theme.dart
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:

lib/components/utils/design_tokens.dart
// 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:

lib/components/utils/themes/definitions/modern_theme.dart
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:

lib/components/utils/themes/definitions/modern_theme.dart
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:

lib/providers/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 *Colors class
  • ✅ 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

On this page