Creating a Custom Context Variant
You will style a widget from runtime context—for example data from an InheritedWidget, theme, or media query—without splitting your UI into separate widgets for each case.
Context variants apply styles when a condition you define is true for the current BuildContext. They complement built-in variants (hover, dark mode, and similar); see Dynamic Styling for those.
Prerequisites
- Flutter:
InheritedWidgetand howBuildContextdepends on ancestors - Mix: Styling (
BoxStyler, chaining) and Dynamic Styling (the.variantAPI and built-in variants)
Understanding Context Variants
A ContextVariant runs its predicate at build time with the current BuildContext. When the predicate returns true, the variant’s styles merge into the base style.
Creating a Basic Context Variant
Here is a small InheritedWidget that exposes a boolean flag:
class CustomInheritedWidget extends InheritedWidget {
final bool flag;
const CustomInheritedWidget({
super.key,
required this.flag,
required super.child,
});
static CustomInheritedWidget? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget>();
}
@override
bool updateShouldNotify(covariant CustomInheritedWidget oldWidget) {
return flag != oldWidget.flag;
}
}Attach a ContextVariant that reads that flag:
BoxStyler()
.color(Colors.red)
.size(100, 100)
.variant(
ContextVariant('custom_flag', (context) {
final flag = CustomInheritedWidget.of(context)?.flag ?? false;
return flag;
}),
BoxStyler().color(Colors.blue),
);In this example:
- The box is red and 100×100 by default.
- When
CustomInheritedWidget.flagis true, the box becomes blue. - The first argument to
ContextVariantis a stable name (for debugging and internal identification). It should stay consistent for the same logical condition if you reuse the variant.
The live preview below uses the same idea: tap the box to flip the flag and see both colors.
Creating Reusable Variant Extensions
BoxStyler and other Mix stylers use WidgetStateVariantMixin, which provides the .variant method. Extensions on that mixin let you add named helpers (similar to .onHovered) and keep call sites readable:
extension WidgetStateVariantMixinX<T extends Style<S>, S extends Spec<S>>
on WidgetStateVariantMixin<T, S> {
T onCustomFlag(T style) {
return variant(
ContextVariant('custom_flag', (context) {
final flag = CustomInheritedWidget.of(context)?.flag ?? false;
return flag;
}),
style,
);
}
}Usage:
BoxStyler()
.color(Colors.red)
.size(100, 100)
.onCustomFlag(
BoxStyler().color(Colors.blue),
);Complete Example
This matches the interactive preview: state toggles the flag, Pressable handles the tap, and Box applies the style.
import 'package:flutter/material.dart';
import 'package:mix/mix.dart';
class CustomInheritedWidget extends InheritedWidget {
final bool flag;
const CustomInheritedWidget({
super.key,
required this.flag,
required super.child,
});
static CustomInheritedWidget? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget>();
}
@override
bool updateShouldNotify(covariant CustomInheritedWidget oldWidget) {
return flag != oldWidget.flag;
}
}
class Example extends StatefulWidget {
const Example({super.key});
@override
State<Example> createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
bool _flag = false;
@override
Widget build(BuildContext context) {
final boxStyle = BoxStyler()
.color(Colors.red)
.size(100, 100)
.borderRounded(10)
.variant(
ContextVariant('custom_flag', (context) {
return CustomInheritedWidget.of(context)?.flag ?? false;
}),
BoxStyler().color(Colors.blue),
);
return CustomInheritedWidget(
flag: _flag,
child: Pressable(
onPress: () => setState(() => _flag = !_flag),
child: Box(style: boxStyle),
),
);
}
}Next steps
- Dynamic Styling — built-in context variants (
onDark,onHovered, breakpoints, and more) - Controlling Widget State — manual state when automatic variants are not enough