Skip to Content
Mix 2.0 is in development! You can access the Mix 1.0 docs here.
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.

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);

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 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)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

MethodDescription
onDark(style)Applies in dark mode
onLight(style)Applies in light mode

Orientation

MethodDescription
onPortrait(style)Applies in portrait orientation
onLandscape(style)Applies in landscape orientation

Breakpoint

MethodDescription
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

MethodDescription
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

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

Advanced

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