Skip to content

Abdo-Nabil/multi_theme_token_approach

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🎨 Flutter Multi‑Brand Theme System (Design Tokens + Codegen + Cubit)

📌 Overview

This project implements a solid, scalable theming architecture for Flutter based on:

  • 🎨 Design tokens in JSON (one file per brand)
  • ⚙️ Build‑time code generation (no runtime parsing)
  • 🧩 Brand theme pairs (light + dark always applied together)
  • 🎛️ Cubit runtime control (switch brand + theme mode)

The goal is to keep UI code free from hex colors and “mystery” Material mappings, while ensuring the designer’s token changes propagate safely and consistently.


🔹 Single-Brand (Simpler Variant)

For applications that do not require multi-brand support, a simplified version of this architecture is available.

👉 Single-brand theme token approach:
https://github.com/Abdo-Nabil/single-theme-token-approach


✅ What This Approach Solves

  • ❌ No hardcoded hex colors in widgets
  • ❌ No duplicated colors across the app
  • ❌ No unclear “which ColorScheme field does this widget use?”
  • ✅ A single source of truth per brand (blue.json, green.json, red.json)
  • ✅ Compile‑time, type‑safe access (context.colors.pageBg, etc.)
  • ✅ Safe runtime switching via Cubit (brand + theme mode)

🏗️ Project Structure (Current Architecture)

lib/
  main.dart
  home_page.dart

  theme/
    design_tokens/                 # 🎨 Designer-owned (one JSON per brand)
      blue.json
      green.json
      red.json

    theme_generator.dart           # ⚙️ Build-time generator (run manually / CI)

    generated_colors/              # 🤖 AUTO-GENERATED (do not edit)
      app_colors.g.dart            # AppColors ThemeExtension (generated contract)
      blue_colors.g.dart           # brand values (light/dark)
      green_colors.g.dart
      red_colors.g.dart
      color_registry.g.dart        # brand+brightness resolver

    core/                          # 🧱 Stable infrastructure
      app_brand.dart               # enum of brands (manual)
      theme_pair.dart              # light/dark pair abstraction (manual)
      context_colors.dart          # BuildContext → AppColors (manual)

    cubit/                         # 🎛️ Runtime state control
      theme_state.dart
      theme_cubit.dart

    app_theme.dart                 # 🎯 ThemeData builder (manual)

Important: this project uses multiple token files (one per brand) — not a single colors.json.


🎨 Design Tokens (One File Per Brand)

Each brand has its own token file:

  • lib/theme/design_tokens/blue.json
  • lib/theme/design_tokens/green.json
  • lib/theme/design_tokens/red.json

Each file uses the same schema:

{
  "themeName": "blue",
  "tokens": {
    "pageBg": { "light": "#F5F9FF", "dark": "#0D1117" },
    "pageFg": { "light": "#121212", "dark": "#EDEDED" },
    "cardBg": { "light": "#FFFFFF", "dark": "#161B22" },

    "buttonPrimaryBg": { "light": "#0065FF", "dark": "#4A8DFF" },
    "buttonPrimaryFg": { "light": "#FFFFFF", "dark": "#000000" },

    "statusSuccess": { "light": "#4CAF50", "dark": "#81C784" },
    "statusWarning": { "light": "#FFC107", "dark": "#FFD54F" },
    "statusError": { "light": "#F44336", "dark": "#FF8A80" }
  }
}

Token Rules ✅

  • All brands must have identical token keys
  • Every token must define both light and dark
  • Colors must be #RRGGBB
  • Token names must be valid Dart identifiers

⚙️ Code Generation (Build‑Time)

The generator reads all *.json files under:

lib/theme/design_tokens/

Run from the project root:

dart lib/theme/theme_generator.dart

This produces:

lib/theme/generated_colors/
  app_colors.g.dart
  <brand>_colors.g.dart
  color_registry.g.dart

Generated Files (Do Not Edit) 🚫

  • app_colors.g.dart: defines AppColors extends ThemeExtension<AppColors>
  • blue_colors.g.dart, green_colors.g.dart, red_colors.g.dart: define const AppColors <brand>LightColors / <brand>DarkColors
  • color_registry.g.dart: maps (AppBrand, isDark)AppColors

🧩 Theme Construction (Theme Pair)

Themes are always applied as a pair:

  • ThemeData for light
  • ThemeData for dark

AppTheme.byBrand(brand) returns a ThemePair:

final pair = AppTheme.byBrand(AppBrand.blue);

MaterialApp(
  theme: pair.light,
  darkTheme: pair.dark,
  themeMode: ThemeMode.system,
);

This ensures light/dark are never mixed across brands.


🎛️ Runtime Switching (Cubit)

The Cubit controls selection, not definition:

  • active brand (AppBrand)
  • theme mode (ThemeMode.system / light / dark)

State

class ThemeState {
  final AppBrand brand;
  final ThemeMode mode;
}

Cubit

context.read<ThemeCubit>().setBrand(AppBrand.green);
context.read<ThemeCubit>().setThemeMode(ThemeMode.dark);

MaterialApp rebuilds based on Cubit state (brand + mode).


🧩 Using Colors in Widgets

Widgets only consume tokens via BuildContext:

final c = context.colors;

Text('Hello', style: TextStyle(color: c.pageFg));

No widget:

  • reads JSON
  • checks brightness
  • branches by brand
  • hardcodes hex values

➕ Adding a New Brand

  1. Add a new file:
lib/theme/design_tokens/purple.json
  1. Add the enum value:
enum AppBrand { blue, green, red, purple }
  1. Run generator:
dart lib/theme/theme_generator.dart
  1. Switch at runtime:
context.read<ThemeCubit>().setBrand(AppBrand.purple);

➖ Removing a Brand

  1. Delete the token file:
lib/theme/design_tokens/red.json
  1. Remove from AppBrand

  2. Run generator:

dart lib/theme/theme_generator.dart
  1. Fix any compile errors where the removed brand was referenced

🛡️ Guarantees

  • ✅ Single source of truth per brand (JSON)
  • ✅ Build‑time validation (generator fails fast)
  • ✅ Compile‑time safety (typed token access)
  • ✅ No runtime parsing overhead
  • ✅ Atomic brand switching (light/dark pairs)
  • ✅ Clean runtime orchestration (Cubit)

🧾 Summary

This repository implements a multi‑brand theming system where designers update brand token JSON files, developers run a build‑time generator, and the app uses typed tokens via context.colors. Runtime theme and brand switching is handled cleanly through Cubit, without leaking theme logic into widgets.

About

Multi theme token approach

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors