Skip to main content
Chromia UI provides built-in support for multi-brand applications, allowing you to maintain different visual identities within the same codebase.

BrandConfig

The BrandConfig class defines brand-specific styling that can be used to generate complete themes.

Constructor

const BrandConfig({
  required String name,
  required Color primaryColor,
  Color? secondaryColor,
  String? logoPath,
  String? fontFamily,
  String? monospaceFontFamily,
  Map<String, Color>? customColors,
})

Properties

name
String
required
The name of the brand (e.g., “Acme Corp”, “Brand A”)
primaryColor
Color
required
The primary brand color used throughout the theme
secondaryColor
Color
Optional secondary brand color. If not provided, a variant of the primary color will be used.
logoPath
String
Path to the brand’s logo asset
fontFamily
String
Custom font family for the brand
monospaceFontFamily
String
Custom monospace font family for code and monospaced text
customColors
Map<String, Color>
Additional custom colors specific to this brand

Creating a Brand

Define your brand configurations:
import 'package:chromia_ui/chromia_ui.dart';
import 'package:flutter/material.dart';

class AppBrands {
  static const acmeCorp = BrandConfig(
    name: 'Acme Corp',
    primaryColor: Color(0xFF2196F3),
    secondaryColor: Color(0xFF03DAC6),
    logoPath: 'assets/logos/acme.png',
    fontFamily: 'Roboto',
    customColors: {
      'accent': Color(0xFFFF5722),
      'highlight': Color(0xFFFFC107),
    },
  );

  static const starlightInc = BrandConfig(
    name: 'Starlight Inc',
    primaryColor: Color(0xFF9C27B0),
    secondaryColor: Color(0xFFE91E63),
    logoPath: 'assets/logos/starlight.png',
    fontFamily: 'Inter',
    customColors: {
      'accent': Color(0xFF673AB7),
      'highlight': Color(0xFFF50057),
    },
  );

  static const techVentures = BrandConfig(
    name: 'Tech Ventures',
    primaryColor: Color(0xFF4CAF50),
    secondaryColor: Color(0xFF8BC34A),
    logoPath: 'assets/logos/techventures.png',
    fontFamily: 'Poppins',
  );
}

Creating Themes from Brands

Use ChromiaThemeData.fromBrand() to generate a complete theme from a brand config:
// Light theme from brand
final acmeLightTheme = ChromiaThemeData.fromBrand(
  AppBrands.acmeCorp,
  isDark: false,
);

// Dark theme from brand
final acmeDarkTheme = ChromiaThemeData.fromBrand(
  AppBrands.acmeCorp,
  isDark: true,
);
The fromBrand factory automatically:
  • Generates a color scheme from the primary color
  • Applies the custom font family to all text styles
  • Configures the monospace font for code
  • Preserves the brand config for later access

Switching Brands at Runtime

Implement brand switching in your application:
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> {
  BrandConfig _currentBrand = AppBrands.acmeCorp;
  bool _isDarkMode = false;

  void switchBrand(BrandConfig newBrand) {
    setState(() {
      _currentBrand = newBrand;
    });
  }

  void toggleDarkMode() {
    setState(() {
      _isDarkMode = !_isDarkMode;
    });
  }

  @override
  Widget build(BuildContext context) {
    final theme = ChromiaThemeData.fromBrand(
      _currentBrand,
      isDark: _isDarkMode,
    );

    return ChromiaTheme(
      data: theme,
      child: MaterialApp(
        theme: theme.toMaterialTheme(),
        home: HomePage(
          onBrandChange: switchBrand,
          onThemeToggle: toggleDarkMode,
        ),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  final Function(BrandConfig) onBrandChange;
  final VoidCallback onThemeToggle;

  const HomePage({
    required this.onBrandChange,
    required this.onThemeToggle,
  });

  @override
  Widget build(BuildContext context) {
    final colors = context.chromiaColors;
    final spacing = context.chromiaSpacing;
    final brandConfig = context.chromiaTheme.brandConfig;

    return Scaffold(
      backgroundColor: colors.background,
      appBar: AppBar(
        title: Text(brandConfig?.name ?? 'App'),
        actions: [
          IconButton(
            icon: Icon(context.isDarkTheme ? Icons.light_mode : Icons.dark_mode),
            onPressed: onThemeToggle,
          ),
          PopupMenuButton<BrandConfig>(
            icon: Icon(Icons.palette),
            onSelected: onBrandChange,
            itemBuilder: (context) => [
              PopupMenuItem(
                value: AppBrands.acmeCorp,
                child: Text('Acme Corp'),
              ),
              PopupMenuItem(
                value: AppBrands.starlightInc,
                child: Text('Starlight Inc'),
              ),
              PopupMenuItem(
                value: AppBrands.techVentures,
                child: Text('Tech Ventures'),
              ),
            ],
          ),
        ],
      ),
      body: Padding(
        padding: spacing.paddingL,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            if (brandConfig?.logoPath != null)
              Image.asset(
                brandConfig!.logoPath!,
                height: 60,
              ),
            spacing.gapVL,
            Text(
              'Welcome to ${brandConfig?.name ?? "our app"}',
              style: context.chromiaTypography.headlineMedium.copyWith(
                color: colors.textPrimary,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Accessing Brand Configuration

Access the current brand config from the theme:
class BrandedWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final theme = context.chromiaTheme;
    final brandConfig = theme.brandConfig;

    if (brandConfig == null) {
      return Text('No brand configured');
    }

    return Column(
      children: [
        Text('Brand: ${brandConfig.name}'),
        if (brandConfig.logoPath != null)
          Image.asset(brandConfig.logoPath!),
        Container(
          color: brandConfig.primaryColor,
          child: Text('Primary Color'),
        ),
      ],
    );
  }
}

Custom Brand Colors

Access custom colors defined in the brand config:
class CustomColorWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final brandConfig = context.chromiaTheme.brandConfig;
    
    // Get custom color by key
    final accentColor = brandConfig?.getCustomColor('accent');
    final highlightColor = brandConfig?.getCustomColor('highlight');

    return Container(
      decoration: BoxDecoration(
        color: accentColor ?? Colors.grey,
        border: Border.all(
          color: highlightColor ?? Colors.grey,
          width: 2,
        ),
      ),
      child: Text('Custom branded element'),
    );
  }
}

Predefined Brand Configurations

Chromia UI includes some predefined brand configurations for testing:
import 'package:chromia_ui/chromia_ui.dart';

// Default Chromia brand
final chromiaTheme = ChromiaThemeData.fromBrand(BrandConfigs.chromia);

// Blue theme
final blueTheme = ChromiaThemeData.fromBrand(BrandConfigs.blueTheme);

// Green theme
final greenTheme = ChromiaThemeData.fromBrand(BrandConfigs.greenTheme);

// Purple theme
final purpleTheme = ChromiaThemeData.fromBrand(BrandConfigs.purpleTheme);

// Orange theme
final orangeTheme = ChromiaThemeData.fromBrand(BrandConfigs.orangeTheme);

// Red theme
final redTheme = ChromiaThemeData.fromBrand(BrandConfigs.redTheme);

// Get all predefined brands
final allBrands = BrandConfigs.all;

Brand-Specific Components

Create components that adapt based on the current brand:
class BrandedButton extends StatelessWidget {
  final String label;
  final VoidCallback onPressed;

  const BrandedButton({
    required this.label,
    required this.onPressed,
  });

  @override
  Widget build(BuildContext context) {
    final brandConfig = context.chromiaTheme.brandConfig;
    final colors = context.chromiaColors;
    final spacing = context.chromiaSpacing;
    final radius = context.chromiaRadius;

    // Different button styles based on brand
    final bool isAcmeBrand = brandConfig?.name == 'Acme Corp';

    return ElevatedButton(
      onPressed: onPressed,
      style: ElevatedButton.styleFrom(
        backgroundColor: colors.primary,
        foregroundColor: colors.onPrimary,
        padding: spacing.symmetric(
          horizontal: isAcmeBrand ? spacing.xl : spacing.l,
          vertical: spacing.m,
        ),
        shape: RoundedRectangleBorder(
          borderRadius: isAcmeBrand 
            ? radius.radiusL 
            : radius.radiusM,
        ),
      ),
      child: Text(label),
    );
  }
}

Persisting Brand Selection

Save and restore the user’s brand choice:
import 'package:shared_preferences/shared_preferences.dart';

class BrandPreferences {
  static const String _keyBrandName = 'selectedBrand';

  static Future<String?> getSavedBrand() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getString(_keyBrandName);
  }

  static Future<void> saveBrand(String brandName) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString(_keyBrandName, brandName);
  }
}

class BrandNotifier extends ChangeNotifier {
  BrandConfig _currentBrand = AppBrands.acmeCorp;

  BrandNotifier() {
    _loadSavedBrand();
  }

  Future<void> _loadSavedBrand() async {
    final savedBrandName = await BrandPreferences.getSavedBrand();
    if (savedBrandName != null) {
      _currentBrand = _getBrandByName(savedBrandName);
      notifyListeners();
    }
  }

  BrandConfig get currentBrand => _currentBrand;

  Future<void> setBrand(BrandConfig brand) async {
    _currentBrand = brand;
    await BrandPreferences.saveBrand(brand.name);
    notifyListeners();
  }

  BrandConfig _getBrandByName(String name) {
    // Map brand names to BrandConfig instances
    switch (name) {
      case 'Acme Corp':
        return AppBrands.acmeCorp;
      case 'Starlight Inc':
        return AppBrands.starlightInc;
      case 'Tech Ventures':
        return AppBrands.techVentures;
      default:
        return AppBrands.acmeCorp;
    }
  }
}

White-Label Applications

For white-label applications, load brand configs from a remote source:
class BrandService {
  static Future<BrandConfig> fetchBrandConfig(String tenantId) async {
    // Fetch brand configuration from API
    final response = await http.get(
      Uri.parse('https://api.example.com/brands/$tenantId'),
    );

    if (response.statusCode == 200) {
      final data = json.decode(response.body);
      return BrandConfig(
        name: data['name'],
        primaryColor: Color(int.parse(data['primaryColor'].substring(1), radix: 16) + 0xFF000000),
        secondaryColor: data['secondaryColor'] != null
          ? Color(int.parse(data['secondaryColor'].substring(1), radix: 16) + 0xFF000000)
          : null,
        logoPath: data['logoUrl'],
        fontFamily: data['fontFamily'],
      );
    }

    throw Exception('Failed to load brand config');
  }
}

class MyApp extends StatefulWidget {
  final String tenantId;

  const MyApp({required this.tenantId});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  BrandConfig? _brandConfig;
  bool _isLoading = true;

  @override
  void initState() {
    super.initState();
    _loadBrand();
  }

  Future<void> _loadBrand() async {
    try {
      final brand = await BrandService.fetchBrandConfig(widget.tenantId);
      setState(() {
        _brandConfig = brand;
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_isLoading) {
      return MaterialApp(
        home: Scaffold(
          body: Center(child: CircularProgressIndicator()),
        ),
      );
    }

    final theme = _brandConfig != null
      ? ChromiaThemeData.fromBrand(_brandConfig!)
      : ChromiaThemeData.light();

    return ChromiaTheme(
      data: theme,
      child: MaterialApp(
        theme: theme.toMaterialTheme(),
        home: HomePage(),
      ),
    );
  }
}

Best Practices

Ensure all brands define the same set of custom colors and properties for consistent component behavior.
Test your application with all brand configurations to ensure components work correctly with different color schemes.
Always provide fallback values when accessing brand-specific properties:
final accentColor = brandConfig?.getCustomColor('accent') ?? colors.primary;
Validate brand configurations to ensure required assets exist:
void validateBrand(BrandConfig brand) {
  assert(brand.name.isNotEmpty, 'Brand name is required');
  assert(brand.primaryColor != null, 'Primary color is required');
  // Add more validations as needed
}

Next Steps

Custom Themes

Learn more about theme customization

Design Tokens

Explore available design tokens

Build docs developers (and LLMs) love