Docs
Guides
Variants

Variants

Variants are a fundamental feature in Mix, streamlining the styling of widgets by defining styling variations that can be applied both conditionally and responsively for dynamic UI adaptation

Key Concepts

Variants are key to customizing widgets in Mix. They address the need for styles that are broadly similar yet have minor, impactful differences.

For example, a button might have primary or secondary styles, or its appearance could vary with screen brightness. These nuanced changes, although subtle, are critical in defining the UI's look and feel, showcasing the versatility and utility of Variants in Mix.

Simple Variants

Simple variants define custom styling variations intended for specific use cases. You can manually apply them to Style instances to exercise fine-grained control.

Defining a Variant

To define a variant, you can use the Variant constructor.

const outlined = Variant('outlined');

Using a Variant in a Style

To use a variant in a style, you can call it as a function on a Style passing the attributes.

const outlined = Variant('outlined');
 
final style = Style(
  $box.padding(20),
  $box.borderRadius(10),
  $box.color.black(),
  $text.style.color.white(),
  outlined(
    $box.color.transparent(),
    $box.border.color.black(),
    $text.style.color.black(),
  ),
);

Applying a Variant

To apply a variant, you can use the applyVariant method on a Style.

const outlined = Variant('outlined');
 
final style = Style(
  $box.padding(20),
  $box.borderRadius(10),
  $box.color.black(),
 
  $text.style.color.white(),
  outlined(
    $box.color.transparent(),
   $box.border.color.black(),
    $text.style.color.black(),
  ),
);
 
final outlinedStyle = style.applyVariant(outlined);

outlinedStyle will now have the attributes defined in the outlined variant, and will be equivalent to the following:

final outlinedStyle = Style(
  $box.padding(20),
  $box.borderRadius(10),
  $box.color.transparent(),
  $box.border.color.black(),
  $text.style.color.black(),
);

Context Variants

Context variants are automatically applied based on the BuildContext. They are useful for creating responsive variants.

Defining a Context Variant

To define a context variant, you can use the ContextVariant constructor and pass a when function.

class OnDarkVariant extends ContextVariant {
  const OnDarkVariant();
 
  @override
  bool build(BuildContext context) {
    return Theme.of(context).brightness == Brightness.dark;
  }
}

Defining the name of a context variant with on is a convention, but not a requirement. That helps identify that this is a ContextVariant.

Using a Context Variant in a Style

To use a context variant in a style, you can call it as a function on a Style passing the attributes.

final style = Style(
  $box.padding(20),
  $box.borderRadius(10),
  $box.color.black(),
  $text.style.color.white(),
  $on.dark(
    $box.color.white(),
    $text.style.color.black(),
  ),
);

When the $on.dark variant is applied, it will override the $box.color and $text.style.color attributes. When this happens the style will be equivalent to the following:

final style = Style(
  $box.padding(20),
  $box.borderRadius(10),
  $box.color.white(),
  $text.style.color.black(),
);

Prioritization

When applying multiple variants to a Style, the last variant will always have priority over the previous ones. However, you can use the priority parameter to change the priority of a ContextVariant, then the highest priority will be last. The priority is a required parameter for ContextVariant that can be set when defining the variant. look at the following example:

class OnCustomVariant extends ContextVariant {
  const OnCustomVariant(): super(priority: VariantPriority.high);
 
  @override
  bool build(BuildContext context) {
    // your condition here
  }
}

The priority parameter can be set to VariantPriority.low, VariantPriority.medium, VariantPriority.high or VariantPriority.highest. A good example of this is when you are using a variants that involves some kind of gesture, like onHover and onPress. In this case, those variants will always have the highest priority. It happens because Mix needs to make sure that the defined styles will be applied correctly.

Conditional operators

The operators | and & can be used to add conditions to your style:

OR Operator

The | operator is used in case you want to apply the variant when any of the variants apply.

final style = Style(
  $box.padding(20),
  ($on.small | $on.medium)( // Whether it's small OR medium
    $box.width(300),
    $box.height(400),
    $box.color.white(),
  ),
);

When the $on.small variant or $on.medium variant is applied, the resulted Style will be equivalent to the following:

Style(
  $box.padding(20),
  $box.width(300),
  $box.height(400),
  $box.color.white(),
);

AND Operator

The & operator is used in case you want to apply the variant when all of the variants apply.

final style = Style(
  $box.padding(20),
  // When it's hovering AND pressing
  (onHover & onPress)(
    $text.style.color.black(),
    $text.style.bold(),
  ),
);

In this scenery only when the onHover variant and onPress are applied, the resulted Style will be equivalent to the following:

Style(
  $box.padding(20),
  $text.style.color.black(),
  $text.style.bold(),
);

Combining operators

You can combine operators | and & to create more complex conditions:

final style = Style(
  $box.height(100),
  $box.width(100),
  (onHover | onPress & $on.dark)(
    $box.color.red(),
  ),
);

The operator that has preference is the first readed operator, so in this case the onHover variant will be applied first, then the onPress and finally the $on.dark variant.

To understand how this works, let's see the following example:

  1. When the onHover variant is applied, the resulted Style will be equivalent to:
Style(
  $box.height(100),
  $box.width(100),
);
  1. However, applying the onHover variant and the $on.dark variant, the resulting Style is different because it satisfies the logic condition in the variant.
Style(
  $box.height(100),
  $box.width(100),
  $box.color.red(),
);

Not Condition

While not a traditional negation operator, onNot allows you to compose a variant that is applied when the ContextVariant apply condition returns false.

final onDisabled = $on.not($on.enabled);
 
final style = Style(
  $on.disabled(
    $with.scale(1.2),
  ),
);

Built-in Context Variants

Mix provides a set of pre-defined context variants for adaptive styling:

Orientation

  • $on.portrait(): Applies styles when the device is in portrait orientation.
  • $on.landscape(): Applies styles when the device is in landscape orientation.

Breakpoints

  • $on.xsmall(): Applies styles when the screen is at least the "extra small" breakpoint.
  • $on.small(): Applies styles when the screen is at least the "small" breakpoint.
  • $on.medium(): Applies styles when the screen is at least the "medium" breakpoint.
  • $on.large(): Applies styles when the screen is at least the "large" breakpoint.

Custom Breakpoints

  • $on.breakpoint({minWidth, maxWidth}): Create custom context variants for specific screen size ranges.

Directionality

  • $on.rtl(): Applies styles when the text direction is right-to-left (RTL).
  • $on.ltr(): Applies styles when the text direction is left-to-right (LTR).

Brightness

  • $on.dark(): Applies styles when the theme is in dark mode.
  • $on.light(): Applies styles when the theme is in light mode.

Widget State

For the following variants, the widget must be wrapped in a Pressable widget or use the GestureBox widget.

  • $on.hover(): Applies styles when the widget is hovered.
  • $on.press(): Applies styles when the widget is pressed.
  • $on.longPress(): Applies styles when the widget is long pressed.
  • $on.focus(): Applies styles when the widget is focused.
  • $on.disabled(): Applies styles when the widget is disabled.
  • $on.enabled(): Applies styles when the widget is enabled.