-
Notifications
You must be signed in to change notification settings - Fork 0
Theming
The layrz_ui theme system provides semantic access to all design tokens — colors, typography, spacing, shadows, borders, and motion. This page explains how the theme is structured and how to use it in widgets.
The theme system consists of three layers:
-
LayrzTheme— theInheritedThemewidget that propagates theme data down the widget tree -
LayrzThemeData— an immutable container holding all tokens and icon theme settings -
LayrzTokens— the complete set of design values (colors, spacing, radius, shadows, etc.)
Every layrz_ui app installs a LayrzTheme via LayrzApp. Once installed, widgets access the theme via BuildContext.theme, BuildContext.tokens, or BuildContext.tokenizer.
LayrzTheme extends InheritedTheme (not plain InheritedWidget) for a specific reason: to survive route and overlay boundaries.
When Flutter crosses an overlay boundary (dialogs, tooltips, dropdowns, menus) or a route boundary, it creates a new widget tree context. Without special handling, an InheritedWidget would be lost, and LayrzTheme.of(context) would fail inside dialogs.
InheritedTheme implements a wrap() method that re-installs the theme below the boundary. This means:
showDialog(
context: context,
builder: (dialogContext) {
// LayrzTheme is accessible here, even though it's in an overlay
final theme = LayrzTheme.of(dialogContext);
return MyDialog();
},
);This works because LayrzApp wraps the app's root, and InheritedTheme ensures the theme survives every route and overlay.
Two static methods provide access:
// Throws if no LayrzTheme is found
final theme = LayrzTheme.of(context);
// Returns null if no LayrzTheme is found
final theme = LayrzTheme.maybeOf(context);LayrzThemeData is an immutable holder of all design values. It is typically not used directly — widgets use the convenience extensions on BuildContext instead.
@immutable
class LayrzThemeData {
/// All color, typography, spacing, radius, shadow, border, and motion tokens.
final LayrzTokens tokens;
/// Icon theme (color, size) applied at the root.
final IconThemeData iconTheme;
// Delegating getters:
Color get primaryColor => tokens.colors.primary;
Color get backgroundColor => tokens.colors.sf1;
Color get surfaceColor => tokens.colors.sf1;
TextStyle get textStyle => tokens.typography.body;
// ... and more
}Use the factory constructor:
LayrzThemeData.light({
Color primaryColor = kPrimaryColor,
LayrzFont titleFont = kLayrzFont,
LayrzFont bodyFont = kLayrzFont,
LayrzFontHandler? fontHandler,
})This wires all derived tokens consistently:
- Color tokens are created first
- Spacing and radius are independent
- Shadow tokens are seeded with the surface color and base radius
- Typography is seeded with the text color and font specifications
- Motion tokens are independent
// Create a light theme with a custom primary color
final theme = LayrzThemeData.light(
primaryColor: const Color(0xFF0077BE), // Custom brand blue
);
// Or modify an existing theme
final customTheme = theme.copyWith(
tokens: theme.tokens.copyWith(
colors: theme.tokens.colors.copyWith(
primary: const Color(0xFF0077BE),
),
),
);LayrzTokens is the aggregate of all semantic design values:
| Member | Type | Contents |
|---|---|---|
colors |
LayrzColorTokens |
Brand, surface, foreground, semantic, and structural colors |
typography |
LayrzTextTheme |
Five text styles (display, headline, title, body, label) |
spacing |
LayrzSpacingTokens |
Five semantic levels (sp1–sp5: 4, 8, 16, 24, 32 logical pixels) plus derived pdN/mgN getters |
radius |
LayrzRadiusTokens |
Five semantic levels (r1–r5: 4, 8, 16, 24, 32 logical pixels) plus full (999, pill), derived brN getters, and innerRadius()
|
shadow |
LayrzShadowTokens |
Elevation levels 0–5 with computed blur and opacity |
border |
LayrzBorderTokens |
Stroke widths and pre-built border sides |
motion |
LayrzMotionTokens |
Durations and easing curves for animations |
Access these directly:
final colors = context.tokens.colors;
final spacing = context.tokens.spacing;
final textStyle = context.tokens.typography.body;
final shadow = context.tokens.shadow.elevation2;The cleanest pattern uses extension methods on BuildContext:
class MyCard extends StatelessWidget {
const MyCard({super.key});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: context.theme.surfaceColor,
borderRadius: BorderRadius.circular(context.theme.borderRadius),
boxShadow: context.tokens.shadow.elevation1,
),
padding: context.tokens.spacing.pd3, // sp3 = 16 logical pixels
child: Text(
'Card content',
style: context.theme.textStyle,
),
);
}
}You can also access the theme directly if you prefer:
final theme = LayrzTheme.of(context);
final primaryColor = theme.primaryColor;For common values, the tokenizer provides flat shortcuts:
final primary = context.tokenizer.primary;
final spacing = context.tokenizer.spacing; // 8.0 (base unit)
final allSpacing = context.tokenizer.spacingTokens; // Full setSee LayrzTokenizer for all available shortcuts.
LayrzThemeData provides delegating getters for the most-used values, maintained for backwards compatibility with layrz_theme:
| Getter | Returns | Equivalent |
|---|---|---|
primaryColor |
Color |
tokens.colors.primary |
backgroundColor |
Color |
tokens.colors.sf1 |
surfaceColor |
Color |
tokens.colors.sf1 |
textColor |
Color |
tokens.colors.fg1 |
hintColor |
Color |
tokens.colors.fg3 |
borderColor |
Color |
tokens.colors.divider |
dangerColor |
Color |
tokens.colors.danger |
successColor |
Color |
tokens.colors.success |
warningColor |
Color |
tokens.colors.warning |
textTheme |
LayrzTextTheme |
tokens.typography |
textStyle |
TextStyle |
tokens.typography.body |
borderRadius |
double |
tokens.radius.r2 (8.0 pixels) — deprecated getter; use tokens.radius.br2 for BorderRadius directly |
iconTheme |
IconThemeData |
The icon theme (color, size) |
New code should access tokens directly. These getters exist to prevent breaking existing call sites.
Every layrz_ui app installs an IconTheme via LayrzApp. The default icon theme uses:
-
Color:
tokens.colors.fg1(primary text color) - Size: 24 logical pixels
Icons render in this color and size unless overridden:
Icon(Icons.star) // Renders at 24px in fg1 color (dark navy text)
Icon(Icons.star, size: 32, color: context.theme.primaryColor) // OverrideThe theme provides five core text styles via tokens.typography. Each style serves a specific purpose and does not offer size variants — developers needing a variant use copyWith(fontSize:) to be explicit:
| Style | Size | Weight | Use Case |
|---|---|---|---|
display |
40px | w700 | Hero text and splash screens |
headline |
24px | w600 | Page-level headings |
title |
20px | w600 | Dialog and card titles |
body |
16px | w400 | Body text and reading passages |
label |
14px | w400 | Labels, buttons, tooltips, badges |
Text(
'Section Heading',
style: context.tokens.typography.headline,
)To change the theme at runtime, rebuild LayrzApp with a new theme:
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Color _brandColor = kPrimaryColor;
void _changeBrandColor(Color newColor) {
setState(() {
_brandColor = newColor;
});
}
@override
Widget build(BuildContext context) {
return LayrzApp(
title: 'My App',
theme: LayrzThemeData.light(primaryColor: _brandColor),
home: HomePage(onChangeColor: _changeBrandColor),
);
}
}When you call setState, LayrzApp rebuilds with the new theme, and all descendants receive the updated tokens.
- D7: Light Mode Only — Explains why dark mode is not supported and why the theme system is designed for light mode only.
Last updated: 2026-08-13
Related pages: Design-Tokens, LayrzTokenizer, LayrzApp, Getting-Started
Made with ❤️ by Golden M, Inc.