-
Notifications
You must be signed in to change notification settings - Fork 0
Theme Extensions
Theme extensions allow components to store custom theme data without polluting the core LayrzThemeData class. This page explains the extension mechanism, why it exists, and how to implement and use custom extensions in your components.
The LayrzThemeExtension<T> mechanism provides a sanctioned way for M2+ components to add their own theme-scoped data. Instead of adding fields directly to LayrzThemeData, a component can define an extension class that holds its tokens and state, register it with the theme, and retrieve it at build time.
Without extensions, there are two bad choices:
-
Add everything to
LayrzThemeData— bloats the core theme with hundreds of component-specific fields. The core theme becomes cluttered and unmaintainable. - Let components hardcode style values — breaks theming consistency and prevents runtime theme changes from affecting components.
Extensions solve this by providing a registry where components can store their own data without modifying LayrzThemeData.
All custom extensions must implement LayrzThemeExtension<T>:
abstract class LayrzThemeExtension<T extends LayrzThemeExtension<T>> {
/// Return the type of this extension (used as the map key).
Object get type => T;
/// Return a copy with some fields changed.
T copyWith({...});
/// Linearly interpolate between two extensions.
T lerp(covariant LayrzThemeExtension<T>? other, double t);
}-
Self-bounded generic
T extends LayrzThemeExtension<T>: This ensures the type is sound and prevents accidental type mismatches at lookup time. -
typegetter returnsT: Used internally as the map key. You do not need to override this; the SDK handles it. -
copyWith(): Returns a copy with specified fields changed. Used for theme customization and immutability. -
lerp(): Interpolates between two extensions. Used for smooth theme animations.
Here's a complete example of a custom button extension:
// lib/buttons/src/button_extension.dart
@immutable
class LayrzButtonExtension extends LayrzThemeExtension<LayrzButtonExtension> {
/// Border radius for all buttons in this theme.
final double borderRadius;
/// Minimum height for buttons.
final double minHeight;
/// Padding for button content.
final EdgeInsets padding;
const LayrzButtonExtension({
this.borderRadius = 8.0,
this.minHeight = 48.0,
this.padding = const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
});
@override
LayrzButtonExtension copyWith({
double? borderRadius,
double? minHeight,
EdgeInsets? padding,
}) {
return LayrzButtonExtension(
borderRadius: borderRadius ?? this.borderRadius,
minHeight: minHeight ?? this.minHeight,
padding: padding ?? this.padding,
);
}
@override
LayrzButtonExtension lerp(
covariant LayrzThemeExtension<LayrzButtonExtension>? other,
double t,
) {
if (other is! LayrzButtonExtension) return this;
return LayrzButtonExtension(
borderRadius: lerpDouble(borderRadius, other.borderRadius, t) ?? borderRadius,
minHeight: lerpDouble(minHeight, other.minHeight, t) ?? minHeight,
padding: EdgeInsets.lerp(padding, other.padding, t) ?? padding,
);
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is LayrzButtonExtension &&
runtimeType == other.runtimeType &&
borderRadius == other.borderRadius &&
minHeight == other.minHeight &&
padding == other.padding;
@override
int get hashCode => Object.hash(borderRadius, minHeight, padding);
}When creating a LayrzThemeData, pass a list of extensions:
final theme = LayrzThemeData.light(
extensions: [
LayrzButtonExtension(borderRadius: 12.0),
LayrzInputExtension(borderColor: Colors.grey),
],
);Alternatively, add extensions to an existing theme via copyWith():
final customTheme = theme.copyWith(
extensions: [
...theme.extensions.values,
LayrzButtonExtension(borderRadius: 12.0),
],
);Use the extension<T>() or maybeExtension<T>() methods on LayrzThemeData (or via the convenience getter on BuildContext):
class LayrzButton extends StatelessWidget {
const LayrzButton({required this.child});
final Widget child;
@override
Widget build(BuildContext context) {
// Option 1: Use maybeExtension (returns null if not found)
final buttonExtension = context.theme.maybeExtension<LayrzButtonExtension>();
final borderRadius = buttonExtension?.borderRadius ?? 8.0;
// Option 2: Use extension (asserts if not found)
final buttonExtension2 = context.theme.extension<LayrzButtonExtension>();
return Container(
padding: buttonExtension?.padding,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius),
),
child: child,
);
}
}-
extension<T>()— Use when the extension is required. Asserts if not found. Name mirrorsLayrzTheme.of(context). -
maybeExtension<T>()— Use when the extension is optional. Returns null if not found. Name mirrorsLayrzTheme.maybeOf(context).
Because extensions are stored on LayrzThemeData and LayrzTheme extends InheritedTheme, extensions automatically flow through Overlay and route boundaries — no special handling needed.
// Inside a dialog, the button extension is still accessible:
showDialog(
context: context,
builder: (dialogContext) {
final buttonExtension = dialogContext.theme.maybeExtension<LayrzButtonExtension>();
// This works because LayrzTheme.wrap() passes the entire LayrzThemeData
return LayrzButton(...);
},
);-
One extension per component category — Define
LayrzButtonExtension,LayrzInputExtension, etc., not one giant extension for all buttons. -
Make extensions immutable — Use
@immutable, implement==andhashCode, and providecopyWith(). -
Use
maybeExtension<T>()for optional customization — Components should work without their extension, using sensible defaults. -
Implement
lerp()for smooth animations — When themes change viasetState(), extensions interpolate smoothly. -
Keep extensions focused — Store only theme-scoped data, not component state. State lives in
WidgetStateor the widget itself.
If migrating from layrz_theme, any custom theme fields you added to ThemedThemeData should become extensions in layrz_ui:
// Old (layrz_theme)
class CustomThemedThemeData extends ThemedThemeData {
final double buttonBorderRadius = 8.0;
}
// New (layrz_ui)
class LayrzButtonExtension extends LayrzThemeExtension<LayrzButtonExtension> {
final double borderRadius = 8.0;
// ...
}See the Theming page for details on how extensions fit into the broader theme system.
For more on theme structure and layering, see architecture.md.
Last updated: 2026-08-14
Related pages: Theming, Design-Tokens, LayrzApp
Made with ❤️ by Golden M, Inc.