Skip to Content
DocsTutorialsCreating Context Variants

Creating a Custom Context Variant

You will style a widget from runtime context—for example data from an InheritedWidget, theme, or media query—without splitting your UI into separate widgets for each case.

Context variants apply styles when a condition you define is true for the current BuildContext. They complement built-in variants (hover, dark mode, and similar); see Dynamic Styling for those.

Prerequisites

  • Flutter: InheritedWidget and how BuildContext depends on ancestors
  • Mix: Styling (BoxStyler, chaining) and Dynamic Styling (the .variant API and built-in variants)

Understanding Context Variants

A ContextVariant runs its predicate at build time with the current BuildContext. When the predicate returns true, the variant’s styles merge into the base style.

Creating a Basic Context Variant

Here is a small InheritedWidget that exposes a boolean flag:

class CustomInheritedWidget extends InheritedWidget { final bool flag; const CustomInheritedWidget({ super.key, required this.flag, required super.child, }); static CustomInheritedWidget? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget>(); } @override bool updateShouldNotify(covariant CustomInheritedWidget oldWidget) { return flag != oldWidget.flag; } }

Attach a ContextVariant that reads that flag:

BoxStyler() .color(Colors.red) .size(100, 100) .variant( ContextVariant('custom_flag', (context) { final flag = CustomInheritedWidget.of(context)?.flag ?? false; return flag; }), BoxStyler().color(Colors.blue), );

In this example:

  • The box is red and 100×100 by default.
  • When CustomInheritedWidget.flag is true, the box becomes blue.
  • The first argument to ContextVariant is a stable name (for debugging and internal identification). It should stay consistent for the same logical condition if you reuse the variant.

The live preview below uses the same idea: tap the box to flip the flag and see both colors.

Resolving preview metadata...

Creating Reusable Variant Extensions

BoxStyler and other Mix stylers use WidgetStateVariantMixin, which provides the .variant method. Extensions on that mixin let you add named helpers (similar to .onHovered) and keep call sites readable:

extension WidgetStateVariantMixinX<T extends Style<S>, S extends Spec<S>> on WidgetStateVariantMixin<T, S> { T onCustomFlag(T style) { return variant( ContextVariant('custom_flag', (context) { final flag = CustomInheritedWidget.of(context)?.flag ?? false; return flag; }), style, ); } }

Usage:

BoxStyler() .color(Colors.red) .size(100, 100) .onCustomFlag( BoxStyler().color(Colors.blue), );

Complete Example

This matches the interactive preview: state toggles the flag, Pressable handles the tap, and Box applies the style.

import 'package:flutter/material.dart'; import 'package:mix/mix.dart'; class CustomInheritedWidget extends InheritedWidget { final bool flag; const CustomInheritedWidget({ super.key, required this.flag, required super.child, }); static CustomInheritedWidget? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget>(); } @override bool updateShouldNotify(covariant CustomInheritedWidget oldWidget) { return flag != oldWidget.flag; } } class Example extends StatefulWidget { const Example({super.key}); @override State<Example> createState() => _ExampleState(); } class _ExampleState extends State<Example> { bool _flag = false; @override Widget build(BuildContext context) { final boxStyle = BoxStyler() .color(Colors.red) .size(100, 100) .borderRounded(10) .variant( ContextVariant('custom_flag', (context) { return CustomInheritedWidget.of(context)?.flag ?? false; }), BoxStyler().color(Colors.blue), ); return CustomInheritedWidget( flag: _flag, child: Pressable( onPress: () => setState(() => _flag = !_flag), child: Box(style: boxStyle), ), ); } }

Next steps