Dynamic Styling
Mix lets you define state-aware styles in one place instead of scattering conditional logic throughout your widget tree. Styles automatically adapt to hover, press, disabled, dark mode, and other contexts.
Understanding Variants
Suppose you want a Box to change its color when hovered. With Mix, you add .onHovered(...) to your style definition — no wrapper needed:
final style = BoxStyler()
.color(Colors.red)
.height(100)
.width(100)
.borderRounded(10)
.onHovered(.color(Colors.blue));Mix automatically merges the hovered style with the base style — you only define what changes.
Composing Styles with Variants
Styles are meant to be reused. When you add a variant to an existing style, it merges with any variant already defined. The new values override, but unrelated properties are preserved:
final styleA = BoxStyler()
.color(Colors.red)
.height(100)
.width(100)
.borderRounded(10)
.onHovered(
.color(Colors.blue)
.width(200)
);
final styleB = styleA.onHovered(.color(Colors.green));styleB inherits everything from styleA. On hover, the color becomes green (overridden) but the width stays 200 (preserved from styleA). The resolved hover style is:
BoxStyler()
.color(Colors.green)
.height(100)
.width(200)
.borderRounded(10);Nesting Variants
You can combine multiple conditions by nesting variants. For example, different hover colors in dark mode vs light mode:
final hoverStyle = BoxStyler()
.onDark(.color(Colors.blue))
.onLight(.color(Colors.green));
final style = BoxStyler()
.color(Colors.red)
.height(100)
.width(100)
.borderRounded(10)
.onHovered(hoverStyle);When hovered in dark mode, the color is blue. When hovered in light mode, the color is green. The base color (red) applies when not hovered.
Variant Resolution Order
Context variants are applied after all regular style properties. This means a variant will always override a regular property — you cannot override a variant’s value by chaining a regular method after it:
// The hover color will be blue, NOT green — the variant wins regardless of order
final style = BoxStyler()
.color(Colors.red)
.onHovered(.color(Colors.blue))
.color(Colors.green); // This overrides the base color, but NOT the hover colorTo override a property set by a variant, you need another variant:
// Correct — use another onHovered to override the hover color
final styleA = BoxStyler()
.color(Colors.red)
.onHovered(.color(Colors.blue));
final styleB = styleA.onHovered(.color(Colors.green)); // Now hover is greenBuilt-in Variants
Interaction
| Method | Description |
|---|---|
onHovered(style) | Applies when the widget is hovered |
onPressed(style) | Applies when the widget is pressed |
onFocused(style) | Applies when the widget is focused (requires Pressable wrapper) |
onDisabled(style) | Applies when the widget is disabled |
onEnabled(style) | Applies when the widget is enabled (not disabled) |
Most interaction variants work on any Mix widget automatically. The exception is onFocused, which requires wrapping your widget in Pressable or using PressableBox, because focus tracking needs Flutter’s Focus widget:
// onFocused needs a Pressable wrapper
Pressable(
onPress: () {},
child: Box(
style: BoxStyler()
.color(Colors.grey)
.onFocused(.color(Colors.blue)),
),
)
// Or use PressableBox for convenience
PressableBox(
onPress: () {},
style: BoxStyler()
.color(Colors.grey)
.onFocused(.color(Colors.blue)),
child: Text('Focus me'),
)Theme
| Method | Description |
|---|---|
onDark(style) | Applies in dark mode |
onLight(style) | Applies in light mode |
Orientation
| Method | Description |
|---|---|
onPortrait(style) | Applies in portrait orientation |
onLandscape(style) | Applies in landscape orientation |
Breakpoint
| Method | Description |
|---|---|
onMobile(style) | Applies on mobile-sized screens |
onTablet(style) | Applies on tablet-sized screens |
onDesktop(style) | Applies on desktop-sized screens |
onBreakpoint(breakpoint, style) | Applies for a custom breakpoint |
Platform
| Method | Description |
|---|---|
onIos(style) | Applies on iOS |
onAndroid(style) | Applies on Android |
onMacos(style) | Applies on macOS |
onWindows(style) | Applies on Windows |
onLinux(style) | Applies on Linux |
onFuchsia(style) | Applies on Fuchsia |
onWeb(style) | Applies on web |
Text Direction
| Method | Description |
|---|---|
onLtr(style) | Applies for left-to-right text direction |
onRtl(style) | Applies for right-to-left text direction |
Advanced
| Method | Description |
|---|---|
onNot(contextVariant, style) | Applies when the given variant is not active |
onBuilder((context) => style) | Builds a style dynamically from BuildContext |
onNot — inverting a condition
onNot negates any ContextVariant. This is useful when you want a style for “everything except” a specific state:
final style = BoxStyler()
.color(Colors.blue)
// Apply white text when NOT in dark mode (same as onLight, but works with any variant)
.onNot(ContextVariant.brightness(Brightness.dark), .color(Colors.white));onBuilder — fully dynamic variants
onBuilder gives you full access to BuildContext to compute a style at build time. Use it when none of the built-in variants cover your condition:
final style = BoxStyler()
.color(Colors.grey)
.onBuilder((context) {
final hour = DateTime.now().hour;
if (hour >= 6 && hour < 18) {
return BoxStyler().color(Colors.amber);
}
return BoxStyler().color(Colors.indigo);
});Going Further
Manual state control: The variants on this page are applied automatically. When you need programmatic control — toggling selected, sharing state across widgets, or custom gesture handling — see Advanced Widget State Control.
Custom context variants: You can create custom ContextVariant instances that respond to any condition from BuildContext — such as an InheritedWidget, a feature flag, or app-specific state. See Creating a Custom Context Variant for a step-by-step walkthrough.