Skip to Content
DocsGuidesDynamic Styling

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.

Resolving preview metadata...

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:

Resolving preview metadata...

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:

Resolving preview metadata...

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 color

To 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 green

Built-in Variants

Interaction

MethodDescription
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

MethodDescription
onDark(style)Style changes when the app is in dark mode
onLight(style)Style changes when the app is in light mode

Orientation

MethodDescription
onPortrait(style)Style changes when the device is in portrait orientation
onLandscape(style)Style changes when the device is in landscape orientation

Breakpoint

MethodDescription
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

MethodDescription
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

MethodDescription
onLtr(style)Style changes for left-to-right text direction
onRtl(style)Style changes for right-to-left text direction

Advanced

MethodDescription
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.