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. This 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 variants that involve some kind of gesture, like onHover
and onPress
. In this case, those variants will always have the highest priority. This 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 resulting 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 scenario, only when the onHover variant and onPress are applied, the resulting 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 read 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:
- When the
onHover
variant is applied, the resultingStyle
will be equivalent to:
Style(
$box.height(100),
$box.width(100),
);
- However, applying the
onHover
variant and the$on.dark
variant, the resultingStyle
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.