LYCN Theme Engine
A reusable Flutter theme engine for managing configurable light, dark, and system themes with custom color tokens, dynamic statuses, runtime theme editing, persistence, JSON import/export, and bundled theme configuration.
Features
- Light, dark, and system theme modes
- Built-in default theme configuration
- Custom color tokens
- Separate light and dark colors for tokens
- Dynamic status definitions and status colors
- Runtime theme customization
- Persistent runtime configuration
- JSON import and export
- Theme reset and seed reset
- Bundled theme configuration through
assets/theme_seed.json - Material 3
ThemeDatageneration - Built-in Theme Manager UI
- Reusable themed widgets
- Theme mode controls
- Customizable status labels
- Custom storage implementations
- Android, iOS, Windows, macOS, and Linux support
Installation
Add lycn_theme_engine to your pubspec.yaml:
dependencies:
lycn_theme_engine: ^1.0.0
Then install the package:
flutter pub get
Quick Start
Create a ThemeManager, initialize it, and provide it to LycnThemeApp:
import 'package:flutter/material.dart';
import 'package:lycn_theme_engine/lycn_theme_engine.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final themeManager = ThemeManager();
await themeManager.initialize();
runApp(
LycnThemeApp(
manager: themeManager,
home: const HomePage(),
),
);
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('LYCN Theme Engine'),
),
body: Center(
child: Text(
'Theme Engine Example',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
),
),
);
}
}
ThemeManager generates the light and dark ThemeData instances and controls the active Flutter theme mode.
Theme Modes
LYCN Theme Engine supports three theme modes:
ThemeModeType.lightThemeModeType.darkThemeModeType.system
Set the theme mode at runtime:
await themeManager.setMode(ThemeModeType.dark);
For system-controlled theme mode:
await themeManager.setMode(ThemeModeType.system);
The Flutter ThemeMode can also be accessed through:
themeManager.flutterThemeMode
Built-in Theme Tokens
The package includes a set of built-in color tokens for common application UI elements.
Examples include:
primary
secondary
tertiary
bg.scaffold
bg.surface
btn.confirm
btn.reject
btn.other
text.title
text.subtitle
text.description
text.other
border.enabled
border.disabled
border.other
status.default
Access commonly used colors directly from ThemeManager:
final primary = themeManager.primary;
final secondary = themeManager.secondary;
final scaffoldBackground = themeManager.bgScaffold;
final surfaceBackground = themeManager.bgSurface;
final titleColor = themeManager.textTitle;
final subtitleColor = themeManager.textSubtitle;
final confirmColor = themeManager.buttonConfirm;
final rejectColor = themeManager.buttonReject;
You can also resolve any token by its key:
final color = themeManager.color('primary');
Custom Color Tokens
Applications can define their own color tokens in addition to the built-in tokens.
For example:
await themeManager.addOrUpdateToken(
TokenDef(
key: 'brand.accent',
label: 'Brand Accent',
),
lightColor: Colors.blue,
darkColor: Colors.lightBlue,
);
Resolve the custom token:
final accentColor = themeManager.color('brand.accent');
Custom tokens support separate light and dark colors.
Check Whether a Token Exists
if (themeManager.hasToken('brand.accent')) {
final color = themeManager.color('brand.accent');
}
Get Token Definitions
final tokens = themeManager.tokenDefs;
for (final token in tokens) {
print(token.key);
}
Remove a Custom Token
await themeManager.removeToken('brand.accent');
Built-in locked tokens cannot be removed.
Dynamic Statuses
LYCN Theme Engine supports dynamic application statuses.
A status contains:
- Actual value
- Display label
- Light-mode color
- Dark-mode color
Example:
await themeManager.addOrUpdateStatus(
StatusDef(
actualValue: 'SUCCESS',
displayLabel: 'Success',
lightColor: Colors.green,
darkColor: Colors.lightGreen,
),
);
Resolve a Status Color
final color = themeManager.statusColor('SUCCESS');
Resolve a Status Label
final label = themeManager.statusLabel('SUCCESS');
Check Whether a Status Exists
if (themeManager.hasStatus('SUCCESS')) {
final color = themeManager.statusColor('SUCCESS');
}
Remove a Status
await themeManager.removeStatus('SUCCESS');
Status colors are also exposed through automatically generated status.<ACTUAL_VALUE> tokens.
For example:
status.SUCCESS
status.PENDING
status.FAILED
Bundled Theme Configuration
Applications can provide a theme configuration that is bundled into the consuming application's build.
Create:
assets/theme_seed.json
Then add the asset to the application's pubspec.yaml:
flutter:
assets:
- assets/theme_seed.json
A seed can contain the application's initial theme configuration, including:
- Theme mode
- Color token definitions
- Status definitions
- Light palette
- Dark palette
Initialize the manager using the seed:
final themeManager = ThemeManager();
await themeManager.initialize(
seed: const ThemeSeed(),
);
The default seed path is:
assets/theme_seed.json
A custom asset path can also be supplied:
await themeManager.initialize(
seed: const ThemeSeed(
assetPath: 'assets/my_theme.json',
),
);
Configuration Precedence
Theme configuration is resolved in the following order:
Built-in defaults
↓
Bundled theme seed
↓
Saved runtime configuration
↓
Final active configuration
This means an application can ship a predefined theme while allowing users to customize it at runtime.
Saved runtime configuration takes precedence over the bundled seed.
Theme Reset
Reset the theme to the built-in defaults:
await themeManager.reset();
Reset to the bundled seed configuration:
await themeManager.resetToSeed();
Clear saved runtime configuration and return to the bundled configuration:
await themeManager.clearSavedConfiguration();
Persistence
Runtime theme configuration is persisted using SharedPreferences by default.
The default storage implementation is:
SharedPreferencesThemeStorage
The package also provides:
ThemeStorage
MemoryThemeStorage
Using Custom Storage
Applications can implement ThemeStorage when a different persistence mechanism is required:
class MyThemeStorage implements ThemeStorage {
@override
Future<void> save(ThemeConfig config) async {
// Save configuration.
}
@override
Future<ThemeConfig?> load() async {
// Load configuration.
return null;
}
@override
Future<void> clear() async {
// Clear configuration.
}
}
Then provide the storage implementation to ThemeManager:
final themeManager = ThemeManager(
storage: MyThemeStorage(),
);
JSON Import and Export
Theme configurations can be exported to JSON.
Export
final json = themeManager.exportJson();
For compact JSON:
final json = themeManager.exportJson(
pretty: false,
);
Import
final success = await themeManager.importJson(json);
if (success) {
print('Theme imported successfully');
}
Invalid JSON does not replace the current configuration.
The package also exposes ThemeJson for direct encoding and decoding of ThemeConfig objects.
Theme Manager UI
The package includes a built-in Theme Manager screen for runtime theme configuration.
Open it using:
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ThemeManagerScreen(
manager: themeManager,
),
),
);
The Theme Manager UI provides:
- Light and dark palette editing
- Theme mode selection
- Built-in token display
- Custom token creation
- Token color editing
- Token deletion for unlocked tokens
- Dynamic status creation
- Status editing
- Status color editing
- Status deletion
- Theme reset
- Reset to bundled seed
- Clear saved configuration
- JSON import
- JSON export
- Live theme preview
The UI adapts to different screen sizes.
Reusable Theme Widgets
LYCN Theme Engine provides convenience widgets that resolve colors and statuses through the active ThemeManager.
Text
TText.title('Page Title');
TText.subtitle('Subtitle');
TText.description('Description');
TText.other('Additional text');
Buttons
TButton.confirm(
label: 'Save',
onPressed: () {},
);
TButton.reject(
label: 'Cancel',
onPressed: () {},
);
TButton.other(
label: 'Other Action',
onPressed: () {},
);
Custom token buttons are also supported:
TButton.token(
tokenKey: 'brand.accent',
label: 'Custom Action',
onPressed: () {},
);
Status Chips
TChip.status('SUCCESS');
The status chip automatically resolves:
- Status color
- Status display label
from the active theme configuration.
Cards
TCard(
child: Text('Content'),
);
Dividers
TDivider();
Theme Mode Controls
The package includes reusable theme mode controls.
Toggle
ThemeModeToggle(
manager: themeManager,
);
The toggle cycles through:
Light → System → Dark → Light
Light/Dark Switch
ThemeModeSwitch(
manager: themeManager,
);
Segmented Control
ThemeModeSegmented(
manager: themeManager,
);
The segmented control provides:
Light | System | Dark
Using ThemeResolver
For applications that need direct token and status resolution, the package provides ThemeResolver.
final resolver = ThemeResolver(themeManager.config);
final color = resolver.color('primary');
final statusColor = resolver.statusColor('SUCCESS');
final statusLabel = resolver.statusLabel('SUCCESS');
The resolver can also resolve colors for a specific theme mode:
final lightColor = resolver.color(
'primary',
mode: ThemeModeType.light,
);
final darkColor = resolver.color(
'primary',
mode: ThemeModeType.dark,
);
Theme Configuration Model
A complete theme configuration consists of:
ThemeConfig
├── mode
├── token definitions
├── status definitions
├── light palette
└── dark palette
Example:
final config = ThemeConfig(
mode: ThemeModeType.system,
tokenDefs: const [
TokenDef(
key: 'brand.accent',
label: 'Brand Accent',
),
],
statuses: const [
StatusDef(
actualValue: 'SUCCESS',
displayLabel: 'Success',
lightColor: Color(0xFF16A34A),
darkColor: Color(0xFF4ADE80),
),
],
light: const {
'brand.accent': Color(0xFF2563EB),
},
dark: const {
'brand.accent': Color(0xFF60A5FA),
},
);
Application Integration
A typical application structure can look like:
my_app/
├── assets/
│ └── theme_seed.json
├── lib/
│ ├── main.dart
│ └── ...
└── pubspec.yaml
pubspec.yaml:
dependencies:
flutter:
sdk: flutter
lycn_theme_engine: ^1.0.0
flutter:
assets:
- assets/theme_seed.json
main.dart:
import 'package:flutter/material.dart';
import 'package:lycn_theme_engine/lycn_theme_engine.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final manager = ThemeManager();
await manager.initialize(
seed: const ThemeSeed(),
);
runApp(
LycnThemeApp(
manager: manager,
home: const HomePage(),
),
);
}
Supported Platforms
LYCN Theme Engine is designed for:
- Android
- iOS
- Windows
- macOS
- Linux
Web is not currently a supported target.
Example Application
The package repository contains an example Flutter application demonstrating the theme engine.
The example demonstrates:
- Theme initialization
- Bundled theme seed configuration
- Light/dark/system modes
- Custom tokens
- Dynamic statuses
- Runtime theme editing
- Theme Manager UI
- Reusable theme widgets
Testing
The package includes unit and widget tests covering the core functionality.
Run the package tests:
flutter test
Run static analysis:
flutter analyze
For deterministic test execution in environments where test concurrency can cause runner issues:
flutter test --concurrency=1
Package Structure
The main public API is available through:
import 'package:lycn_theme_engine/lycn_theme_engine.dart';
The package contains the following major components:
ThemeManager
ThemeResolver
ThemeConfig
TokenDef
StatusDef
ThemeSeed
ThemeStorage
ThemeJson
ThemeDataBuilder
LycnThemeApp
ThemeInitializer
ThemeManagerScreen
TText
TButton
TChip
TCard
TDivider
ThemeModeToggle
ThemeModeSwitch
ThemeModeSegmented
Version
Current version:
1.0.0
Repository
Source code, examples, tests, and issue tracking are available on GitHub:
https://github.com/LycnAlpha/lycn_theme_engine
License
LYCN Theme Engine is released under the MIT License.
See the LICENSE file for the complete license text.