Skip to main content
Chromia UI makes it easy to create custom themes that match your brand identity while maintaining consistency and accessibility.

Creating a Custom Theme

There are several approaches to creating custom themes:

1. Modifying Built-in Themes

The simplest approach is to start with a built-in theme and customize it:
import 'package:chromia_ui/chromia_ui.dart';
import 'package:flutter/material.dart';

final customTheme = ChromiaThemeData.light().copyWith(
  colors: ChromiaThemeData.light().colors.copyWith(
    primary: Color(0xFF6200EE),
    secondary: Color(0xFF03DAC6),
  ),
);

2. Building from Primary Color

Create a theme from a single primary color:
final customColors = ChromiaColors.fromPrimary(
  Color(0xFF6200EE),
  isDark: false,
);

final customTheme = ChromiaThemeData.light().copyWith(
  colors: customColors,
);

3. Complete Custom Configuration

For full control, create a completely custom theme:
final customColors = ChromiaColors(
  // Brand colors
  primary: Color(0xFF6200EE),
  primaryHover: Color(0xFF7722FF),
  primaryPressed: Color(0xFF4500BB),
  primaryDisabled: Color(0xFFD5B8FF),
  onPrimary: Color(0xFFFFFFFF),
  secondary: Color(0xFF03DAC6),
  secondaryHover: Color(0xFF00E5CC),
  secondaryPressed: Color(0xFF00B8A0),
  secondaryDisabled: Color(0xFFB2F2EE),
  onSecondary: Color(0xFF000000),
  // ... additional colors
  surface: Color(0xFFFFFFFF),
  surfaceHover: Color(0xFFF5F5F5),
  surfacePressed: Color(0xFFEEEEEE),
  surfaceVariant: Color(0xFFF5F5F5),
  surfaceContainer: Color(0xFFEEEEEE),
  surfaceContainerHigh: Color(0xFFEEEEEE),
  surfaceContainerHighest: Color(0xFFE0E0E0),
  onSurface: Color(0xFF2A2A2A),
  onSurfaceVariant: Color(0xFF616161),
  background: Color(0xFFFAFAFA),
  onBackground: Color(0xFF2A2A2A),
  // State colors
  success: Color(0xFF43A047),
  successHover: Color(0xFF388E3C),
  onSuccess: Color(0xFFFFFFFF),
  warning: Color(0xFFFB8C00),
  warningHover: Color(0xFFF57C00),
  onWarning: Color(0xFFFFFFFF),
  error: Color(0xFFE53935),
  errorHover: Color(0xFFD32F2F),
  onError: Color(0xFFFFFFFF),
  info: Color(0xFF00ACC1),
  infoHover: Color(0xFF0097A7),
  onInfo: Color(0xFFFFFFFF),
  // Borders and dividers
  border: Color(0xFFE0E0E0),
  borderHover: Color(0xFFBDBDBD),
  borderStrong: Color(0xFF9E9E9E),
  divider: Color(0xFFEEEEEE),
  // Text colors
  textPrimary: Color(0xFF2A2A2A),
  textSecondary: Color(0xFF616161),
  textTertiary: Color(0xFF9E9E9E),
  textDisabled: Color(0xFF9E9E9E),
  // Other
  shadow: Color(0xFF2A2A2A),
  overlay: Color(0x52000000),
  scrim: Color(0x52000000),
  transparent: Color(0x00FFFFFF),
);

final customTheme = ChromiaThemeData(
  colors: customColors,
  typography: ChromiaTypography.defaultTypography(),
  spacing: ChromiaSpacing.defaultSpacing(),
  radius: ChromiaRadius.defaultRadius(),
  shadows: ChromiaShadows.light(),
  brightness: Brightness.light,
);

Customizing Typography

Create custom typography with your own font family and sizes:
import 'package:google_fonts/google_fonts.dart';

final customTypography = ChromiaTypography(
  displayLarge: GoogleFonts.inter(
    fontSize: 72,
    fontWeight: FontWeight.w700,
    height: 1.2,
    letterSpacing: -0.5,
  ),
  displayMedium: GoogleFonts.inter(
    fontSize: 56,
    fontWeight: FontWeight.w700,
    height: 1.2,
  ),
  // ... other text styles
  bodyLarge: GoogleFonts.inter(
    fontSize: 16,
    fontWeight: FontWeight.w400,
    height: 1.5,
  ),
  bodyMedium: GoogleFonts.inter(
    fontSize: 14,
    fontWeight: FontWeight.w400,
    height: 1.5,
  ),
  code: GoogleFonts.robotoMono(
    fontSize: 14,
    fontWeight: FontWeight.w400,
    height: 1.75,
  ),
  // ... additional styles
);

final customTheme = ChromiaThemeData.light().copyWith(
  typography: customTypography,
);

Customizing Spacing

Adjust spacing values to match your design requirements:
final customSpacing = ChromiaSpacing(
  none: 0,
  xxs: 2,
  xs: 4,
  s: 8,
  m: 16,   // Increased from default 12
  l: 24,   // Increased from default 16
  xl: 32,  // Increased from default 20
  xxl: 40, // Increased from default 24
  xxxl: 48, // Increased from default 32
  huge: 64,
  xhuge: 80,
  xxhuge: 96,
  massive: 128,
);

final customTheme = ChromiaThemeData.light().copyWith(
  spacing: customSpacing,
);

Customizing Border Radius

Create more or less rounded corners throughout your app:
// More rounded (modern look)
final roundedRadius = ChromiaRadius(
  none: 0,
  xs: 4,
  s: 8,
  m: 12,
  l: 16,
  xl: 24,
  xxl: 32,
  full: 9999,
);

// Less rounded (classic look)
final sharpRadius = ChromiaRadius(
  none: 0,
  xs: 2,
  s: 4,
  m: 6,
  l: 8,
  xl: 10,
  xxl: 12,
  full: 9999,
);

final customTheme = ChromiaThemeData.light().copyWith(
  radius: roundedRadius,
);

Customizing Shadows

Adjust shadow intensity for different elevation effects:
final subtleShadows = ChromiaShadows(
  none: [],
  xs: [
    BoxShadow(
      color: Color(0x0D000000), // Lower opacity
      blurRadius: 2,
      offset: Offset(0, 1),
    ),
  ],
  s: [
    BoxShadow(
      color: Color(0x0D000000),
      blurRadius: 4,
      offset: Offset(0, 2),
    ),
  ],
  m: [
    BoxShadow(
      color: Color(0x0D000000),
      blurRadius: 8,
      offset: Offset(0, 4),
    ),
  ],
  // ... other shadow levels
);

final customTheme = ChromiaThemeData.light().copyWith(
  shadows: subtleShadows,
);

Theme Presets

Create reusable theme presets for different use cases:
class AppThemes {
  // Modern theme with vibrant colors
  static ChromiaThemeData get modern {
    return ChromiaThemeData.light().copyWith(
      colors: ChromiaColors.fromPrimary(
        Color(0xFF6200EE),
        isDark: false,
      ),
      radius: ChromiaRadius(
        none: 0,
        xs: 4,
        s: 8,
        m: 16,
        l: 24,
        xl: 32,
        xxl: 40,
        full: 9999,
      ),
    );
  }

  // Classic theme with subtle colors
  static ChromiaThemeData get classic {
    return ChromiaThemeData.light().copyWith(
      colors: ChromiaColors.fromPrimary(
        Color(0xFF1976D2),
        isDark: false,
      ),
      radius: ChromiaRadius(
        none: 0,
        xs: 2,
        s: 4,
        m: 6,
        l: 8,
        xl: 10,
        xxl: 12,
        full: 9999,
      ),
    );
  }

  // Minimal theme with lots of whitespace
  static ChromiaThemeData get minimal {
    return ChromiaThemeData.light().copyWith(
      spacing: ChromiaSpacing(
        none: 0,
        xxs: 4,
        xs: 8,
        s: 16,
        m: 24,
        l: 32,
        xl: 48,
        xxl: 64,
        xxxl: 80,
        huge: 96,
        xhuge: 128,
        xxhuge: 160,
        massive: 200,
      ),
    );
  }
}

// Usage
final theme = AppThemes.modern;

Matching Dark Theme

When creating a custom light theme, create a matching dark theme:
class MyCustomTheme {
  static ChromiaThemeData light() {
    final customColors = ChromiaColors.fromPrimary(
      Color(0xFF6200EE),
      isDark: false,
    );

    return ChromiaThemeData(
      colors: customColors,
      typography: ChromiaTypography.defaultTypography(),
      spacing: ChromiaSpacing.defaultSpacing(),
      radius: ChromiaRadius.defaultRadius(),
      shadows: ChromiaShadows.light(),
      brightness: Brightness.light,
    );
  }

  static ChromiaThemeData dark() {
    final customColors = ChromiaColors.fromPrimary(
      Color(0xFF6200EE),
      isDark: true,
    );

    return ChromiaThemeData(
      colors: customColors,
      typography: ChromiaTypography.defaultTypography(),
      spacing: ChromiaSpacing.defaultSpacing(),
      radius: ChromiaRadius.defaultRadius(),
      shadows: ChromiaShadows.dark(),
      brightness: Brightness.dark,
    );
  }
}

// Usage
final lightTheme = MyCustomTheme.light();
final darkTheme = MyCustomTheme.dark();

Using Custom Fonts

Integrate custom fonts using Google Fonts or local font files:
import 'package:google_fonts/google_fonts.dart';

final customTypography = ChromiaTypography.defaultTypography().copyWith(
  displayLarge: GoogleFonts.playfairDisplay(
    fontSize: 72,
    fontWeight: FontWeight.w700,
    height: 1.2,
    letterSpacing: -0.5,
  ),
  bodyLarge: GoogleFonts.inter(
    fontSize: 16,
    fontWeight: FontWeight.w400,
    height: 1.5,
  ),
);

Theming Best Practices

When creating custom colors, maintain the semantic naming convention (primary, secondary, success, error, etc.) to ensure components work correctly.
Ensure your custom colors meet WCAG contrast requirements:
// Use Flutter's contrast ratio calculator
import 'package:flutter/material.dart';

final ratio = ThemeData.estimateBrightnessForColor(
  foreground: colors.textPrimary,
  background: colors.surface,
);

// Aim for at least 4.5:1 for normal text
// and 3:1 for large text
Use a consistent spacing scale (like the 4px or 8px grid) to maintain visual harmony.
Create documentation for your custom theme to help other developers understand the design system:
/// Custom theme for the MyApp application.
/// 
/// Uses a purple primary color (#6200EE) and increased
/// spacing for a more spacious feel.
/// 
/// Example:
/// ```dart
/// ChromiaTheme(
///   data: MyAppTheme.light(),
///   child: MaterialApp(...),
/// )
/// ```
class MyAppTheme {
  // ...
}

Complete Example

Here’s a complete example of a custom theme implementation:
import 'package:chromia_ui/chromia_ui.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';

class ECommerceTheme {
  // Brand colors
  static const Color _brandPrimary = Color(0xFFE91E63);
  static const Color _brandSecondary = Color(0xFF9C27B0);

  static ChromiaThemeData light() {
    // Custom colors based on brand
    final colors = ChromiaColors.fromPrimary(
      _brandPrimary,
      isDark: false,
    ).copyWith(
      secondary: _brandSecondary,
    );

    // Custom typography with brand fonts
    final typography = ChromiaTypography(
      displayLarge: GoogleFonts.playfairDisplay(
        fontSize: 72,
        fontWeight: FontWeight.w700,
        height: 1.2,
        letterSpacing: -0.5,
      ),
      displayMedium: GoogleFonts.playfairDisplay(
        fontSize: 56,
        fontWeight: FontWeight.w700,
        height: 1.2,
      ),
      displaySmall: GoogleFonts.playfairDisplay(
        fontSize: 48,
        fontWeight: FontWeight.w600,
        height: 1.2,
      ),
      headlineLarge: GoogleFonts.inter(
        fontSize: 40,
        fontWeight: FontWeight.w600,
        height: 1.2,
      ),
      headlineMedium: GoogleFonts.inter(
        fontSize: 32,
        fontWeight: FontWeight.w600,
        height: 1.5,
      ),
      headlineSmall: GoogleFonts.inter(
        fontSize: 28,
        fontWeight: FontWeight.w500,
        height: 1.5,
      ),
      titleLarge: GoogleFonts.inter(
        fontSize: 24,
        fontWeight: FontWeight.w500,
        height: 1.5,
      ),
      titleMedium: GoogleFonts.inter(
        fontSize: 20,
        fontWeight: FontWeight.w500,
        height: 1.5,
      ),
      titleSmall: GoogleFonts.inter(
        fontSize: 18,
        fontWeight: FontWeight.w500,
        height: 1.5,
      ),
      bodyLarge: GoogleFonts.inter(
        fontSize: 16,
        fontWeight: FontWeight.w400,
        height: 1.5,
      ),
      bodyMedium: GoogleFonts.inter(
        fontSize: 14,
        fontWeight: FontWeight.w400,
        height: 1.5,
      ),
      bodySmall: GoogleFonts.inter(
        fontSize: 12,
        fontWeight: FontWeight.w400,
        height: 1.5,
      ),
      labelLarge: GoogleFonts.inter(
        fontSize: 16,
        fontWeight: FontWeight.w500,
        height: 1.5,
        letterSpacing: 0.5,
      ),
      labelMedium: GoogleFonts.inter(
        fontSize: 14,
        fontWeight: FontWeight.w500,
        height: 1.5,
        letterSpacing: 0.5,
      ),
      labelSmall: GoogleFonts.inter(
        fontSize: 12,
        fontWeight: FontWeight.w500,
        height: 1.5,
        letterSpacing: 0.5,
      ),
      caption: GoogleFonts.inter(
        fontSize: 12,
        fontWeight: FontWeight.w400,
        height: 1.5,
      ),
      overline: GoogleFonts.inter(
        fontSize: 10,
        fontWeight: FontWeight.w500,
        height: 1.5,
        letterSpacing: 1.5,
      ),
      code: GoogleFonts.robotoMono(
        fontSize: 14,
        fontWeight: FontWeight.w400,
        height: 1.75,
      ),
    );

    // More rounded for modern e-commerce feel
    final radius = ChromiaRadius(
      none: 0,
      xs: 4,
      s: 8,
      m: 16,
      l: 20,
      xl: 24,
      xxl: 32,
      full: 9999,
    );

    return ChromiaThemeData(
      colors: colors,
      typography: typography,
      spacing: ChromiaSpacing.defaultSpacing(),
      radius: radius,
      shadows: ChromiaShadows.light(),
      brightness: Brightness.light,
    );
  }

  static ChromiaThemeData dark() {
    final colors = ChromiaColors.fromPrimary(
      _brandPrimary,
      isDark: true,
    ).copyWith(
      secondary: _brandSecondary,
    );

    // Use same typography and radius as light theme
    final lightTheme = light();

    return ChromiaThemeData(
      colors: colors,
      typography: lightTheme.typography,
      spacing: ChromiaSpacing.defaultSpacing(),
      radius: lightTheme.radius,
      shadows: ChromiaShadows.dark(),
      brightness: Brightness.dark,
    );
  }
}

// Usage in app
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChromiaTheme(
      data: ECommerceTheme.light(),
      child: MaterialApp(
        theme: ECommerceTheme.light().toMaterialTheme(),
        darkTheme: ECommerceTheme.dark().toMaterialTheme(),
        home: HomePage(),
      ),
    );
  }
}

Next Steps

Multi-Brand

Support multiple brands in one app

Design Tokens

Explore all available design tokens

Build docs developers (and LLMs) love