Skip to Content
Mix 2.0 is in development! You can access the Mix 1.0 docs here.
DocsGuidesWidget Modifiers

Widget Modifiers

Modifiers wrap a widget with another widget (like Transform, Padding, or Opacity) to add effects without changing the widget tree. Use the .wrap() method with a WidgetModifierConfig class.

How Modifiers Work

Use .wrap() with a modifier config to wrap a widget:

import 'package:flutter/material.dart'; import 'package:mix/mix.dart'; final style = BoxStyler() .color(Colors.red) .size(100, 100) .wrap(.opacity(0.4));

This produces a widget tree equivalent to Opacity(opacity: 0.4, child: Container(...)).

Built-in Modifiers

ModifierDescriptionWrapper Widget
.opacity(value)Sets opacityOpacity
.padding(insets)Adds paddingPadding
.align(alignment)Sets alignmentAlign
.aspectRatio(ratio)Constrains aspect ratioAspectRatio
.flexible(flex: flex, fit: fit)Makes flexible in Flex layoutsFlexible
.transform(matrix)Applies transformTransform
.visibility(visible: visible)Shows/hides widgetVisibility
.clipRect()Clips to rectangleClipRect
.clipRRect(borderRadius: radius)Clips to rounded rectangleClipRRect
.clipOval()Clips to ovalClipOval

Usage Examples

import 'package:flutter/material.dart'; import 'package:mix/mix.dart'; // Combine multiple modifiers final cardStyle = BoxStyler() .color(Colors.white) .size(200, 100) .wrap(.opacity(0.9)) .wrap(.padding(.all(16))) .wrap(.align(alignment: .center)); // Use transform methods for scale/rotate final buttonStyle = BoxStyler() .color(Colors.blue) .paddingAll(16) .scale(1.0) .onHovered(.scale(1.1));

Modifier Ordering

Built-in modifiers follow a predefined ordering defined by the framework, regardless of the order you chain them. Mix enforces a specific modifier order to ensure consistent widget tree structure:

  1. Context & BehaviorFlexible, Visibility, IconTheme, DefaultTextStyle
  2. Size EstablishmentSizedBox, FractionallySizedBox, IntrinsicWidth/Height
  3. PositioningAlign, Padding, AspectRatio
  4. Visual EffectsOpacity, Transform, ClipRect/RRect/Oval
// These two produce the same widget tree — chain order has no effect final a = BoxStyler() .wrap(.opacity(0.5)) .wrap(.padding(.all(8))); final b = BoxStyler() .wrap(.padding(.all(8))) .wrap(.opacity(0.5));

The framework reorders modifiers according to its internal ordering rules. If you need explicit control over ordering, create a custom modifier with a specific position in the tree.

Creating Custom Modifiers

Create custom modifiers with two classes:

  1. WidgetModifier - Builds the wrapper widget
  2. WidgetModifierConfig (via ModifierMix) - Handles merging and resolving

Step 1: Create the WidgetModifier

import 'package:flutter/widgets.dart'; import 'package:mix/mix.dart'; /// Modifier that applies opacity to its child. final class OpacityModifier extends WidgetModifier<OpacityModifier> { /// Opacity value between 0.0 and 1.0 (inclusive). final double opacity; const OpacityModifier([double? opacity]) : opacity = opacity ?? 1.0; @override OpacityModifier copyWith({double? opacity}) { return OpacityModifier(opacity ?? this.opacity); } @override OpacityModifier lerp(OpacityModifier? other, double t) { if (other == null) return this; // Linear interpolation for smooth animations return OpacityModifier(MixOps.lerp(opacity, other.opacity, t)!); } @override List<Object?> get props => [opacity]; @override Widget build(Widget child) { return Opacity(opacity: opacity, child: child); } }

Key methods to implement:

MethodPurpose
copyWithCreates a copy with optional property updates
lerpLinear interpolation for animations between two modifier states
propsList of properties for equality comparison
buildConstructs the wrapper widget

Step 2: Create the ModifierMix

The ModifierMix class makes the modifier mergeable and resolvable. It extends ModifierMix<T> and gets wrapped in a WidgetModifierConfig when passed to .wrap(). It uses Prop<T> for properties to support tokens and lazy resolution.

import 'package:flutter/widgets.dart'; import 'package:mix/mix.dart'; /// Mix class for applying opacity modifications. class OpacityModifierMix extends ModifierMix<OpacityModifier> { final Prop<double>? opacity; const OpacityModifierMix.create({this.opacity}); /// Convenience constructor with direct value OpacityModifierMix({double? opacity}) : this.create(opacity: Prop.maybe(opacity)); @override OpacityModifier resolve(BuildContext context) { return OpacityModifier(MixOps.resolve(context, opacity)); } @override OpacityModifierMix merge(OpacityModifierMix? other) { if (other == null) return this; return OpacityModifierMix.create( opacity: MixOps.merge(opacity, other.opacity), ); } @override List<Object?> get props => [opacity]; }

Key methods:

MethodPurpose
resolveResolves Prop values using BuildContext and creates the final WidgetModifier
mergeCombines two instances, with the other’s values taking precedence
propsList of properties for equality comparison

Step 3: Use the Custom Modifier

Pass an instance of your ModifierMix class to .wrap():

import 'package:flutter/material.dart'; import 'package:mix/mix.dart'; // Pass the custom ModifierMix instance to .wrap() final style = BoxStyler() .color(Colors.red) .size(100, 100) .wrap(OpacityModifierMix(opacity: 0.4));

Mix ships with built-in modifier shorthands like .wrap(.opacity(0.4)). When you create a custom modifier, you use .wrap(YourModifierMix(...)) with the full class name instead.

Best Practices

  • Use modifiers for transforms, visibility, layout adjustments, and clipping
  • Use styler methods (not modifiers) for colors, borders, shadows
  • Avoid deeply nested modifier chains
  • Implement lerp properly for smooth animations