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.
This guide builds on the Styling guide. If you haven’t read it yet, start there to learn how Style and Styler work.
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);Hover over each box below to see the difference — styleA turns blue while styleB turns green, but both expand to 200 width:
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. Toggle the mode and hover to see the difference:
Variant Resolution Order
Now that you’ve seen how variants compose, here’s how Mix decides which style wins.
Context variants are applied after all regular style properties. This means a variant always overrides 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) | Style changes when the cursor hovers over the widget |
onPressed(style) | Style changes while the widget is being pressed |
onFocused(style) | Style changes when the widget receives focus (requires Pressable wrapper) |
onDisabled(style) | Style changes when the widget is disabled |
onEnabled(style) | Style changes when the widget is 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) | Style changes when the app is in dark mode |
onLight(style) | Style changes when the app is in light mode |
Orientation
| Method | Description |
|---|---|
onPortrait(style) | Style changes when the device is in portrait orientation |
onLandscape(style) | Style changes when the device is in landscape orientation |
Breakpoint
| Method | Description |
|---|---|
onMobile(style) | Style changes on screens narrower than the mobile breakpoint |
onTablet(style) | Style changes on screens within the tablet breakpoint range |
onDesktop(style) | Style changes on screens wider than the desktop breakpoint |
onBreakpoint(breakpoint, style) | Style changes for a custom breakpoint |
Platform
| Method | Description |
|---|---|
onIos(style) | Style changes on iOS |
onAndroid(style) | Style changes on Android |
onMacos(style) | Style changes on macOS |
onWindows(style) | Style changes on Windows |
onLinux(style) | Style changes on Linux |
onFuchsia(style) | Style changes on Fuchsia |
onWeb(style) | Style changes on web |
Text Direction
| Method | Description |
|---|---|
onLtr(style) | Style changes for left-to-right text direction |
onRtl(style) | Style changes for right-to-left text direction |
Advanced
| Method | Description |
|---|---|
onNot(contextVariant, style) | Style changes 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)
// Use white color when NOT in dark mode
.onNot(ContextVariant.brightness(Brightness.dark), .color(Colors.white));You can negate any variant — widget states, breakpoints, platforms, and more:
// Only apply a shadow when NOT on mobile
final style = BoxStyler()
.onNot(ContextVariant.mobile(), .shadow(
color: Colors.black26,
blurRadius: 10,
));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.