Mix
Guides

Directives

Transform text and numeric values at style resolution time with Mix directives.

Prerequisites: This is an advanced guide. You should be familiar with Styling and Design Tokens before reading this.

You could transform text with toUpperCase() or compute sizes with * 2 before passing them to a style — but those transformations live outside the style, so they get lost when styles merge or resolve tokens. Directives keep transformations inside the style, making them composable, merge-safe, and token-aware.

Directives transform values (text, numbers, colors) when a style is resolved. Unlike modifiers (which wrap widgets), directives transform the underlying data.

// The text "hello world" is transformed to "HELLO WORLD" at resolve time
StyledText(
  'hello world',
  style: TextStyler().uppercase(),
)

Text Directives

Transform strings in StyledText widgets. Text directives are called directly on TextStyler:

MethodDescriptionInputOutput
.uppercase()All characters to uppercase"hello world""HELLO WORLD"
.lowercase()All characters to lowercase"Hello World""hello world"
.capitalize()First letter of each word"hello world""Hello World"
.titlecase()Title case format"hello world""Hello World"
.sentencecase()First letter of first word"hello world""Hello world"
// Uppercase text
StyledText(
  'welcome back',
  style: TextStyler().uppercase().fontSize(18),
)

// Title case for headings
StyledText(
  'user profile settings',
  style: TextStyler().titlecase().fontWeight(.bold),
)

// Sentence case for descriptions
StyledText(
  'click here to continue',
  style: TextStyler().sentencecase().color(Colors.grey),
)
Resolving preview metadata...

Number Directives

Transform numeric values (width, height, padding, font sizes). Number directives are chained on Prop values inside .create():

// .create() gives you access to the Prop API, where directives live
final style = BoxStyler.create(
  width: Prop.value(10.0).multiply(2).add(5).clamp(0, 30),
);

Number directives require .create() instead of the fluent API (.width(...)) because directives are methods on Prop, not on the styler itself.

Available number directives

MethodDescriptionExample
.multiply(factor)Multiplies by a factorProp.value(10).multiply(2) → 20
.add(addend)Adds a valueProp.value(10).add(5) → 15
.subtract(value)Subtracts a valueProp.value(10).subtract(3) → 7
.divide(divisor)Divides by a valueProp.value(10).divide(2) → 5
.clamp(min, max)Constrains between boundsProp.value(25).clamp(0, 20) → 20
.abs()Returns absolute valueProp.value(-10).abs() → 10
.round()Rounds to nearest integerProp.value(15.7).round() → 16
.floor()Rounds downProp.value(15.7).floor() → 15
.ceil()Rounds upProp.value(15.3).ceil() → 16
.scale(ratio)Alias for multiplyProp.value(10).scale(1.5) → 15

Usage examples

// Dynamic sizing with tokens — the token resolves first, then the directive applies
final $baseSize = DoubleToken('base.size');

final boxStyle = BoxStyler.create(
  width: Prop.token($baseSize).multiply(2),
  height: Prop.token($baseSize).multiply(1.5),
);

// Clamped responsive values
final responsiveStyle = BoxStyler.create(
  padding: Prop.value(16.0).clamp(8, 32),
);

// Chaining multiple directives (applied left to right)
final chainedStyle = BoxStyler.create(
  width: Prop.value(100.0).multiply(2).add(20).clamp(0, 300),
);

Division always returns a double in Dart. The divisor cannot be zero.

Chaining order matters — directives apply left to right:

Prop.value(10).add(5).multiply(2)  // (10 + 5) * 2 = 30
Prop.value(10).multiply(2).add(5)  // (10 * 2) + 5 = 25

Color Directives

Transform colors by adjusting opacity, brightness, saturation, and more. Color directives are called directly on color styler methods:

MethodDescription
.withOpacity(double)Set opacity (0.0–1.0)
.withAlpha(int)Set alpha channel (0–255)
.darken(double)Darken by percentage
.lighten(double)Lighten by percentage
.saturate(double)Increase saturation
.desaturate(double)Decrease saturation
.tint(double)Mix with white
.shade(double)Mix with black
.brighten(double)Increase brightness
final style = BoxStyler()
    .color(Colors.blue.withOpacity(0.5));

// Color directives also work with tokens
final style = BoxStyler()
    .color($primary().withOpacity(0.8));

Creating Custom Directives

Extend Directive<T> to create application-specific transformations. Each directive needs three things:

  • apply() — the transformation logic
  • key — a unique string identifier used for equality and merging
  • == / hashCode — required so Mix can deduplicate directives when styles merge

Text directive example

/// Directive that reverses a string
final class ReverseStringDirective extends Directive<String> {
  const ReverseStringDirective();

  @override
  String apply(String value) => value.split('').reversed.join();

  @override
  String get key => 'reverse';

  @override
  bool operator ==(Object other) =>
      identical(this, other) || other is ReverseStringDirective;

  @override
  int get hashCode => key.hashCode;
}

// Expose it as a fluent method on TextStyler
extension ReverseTextDirective on TextStyler {
  TextStyler reverse() {
    return merge(
      .textDirective(const ReverseStringDirective()),
    );
  }
}

// Usage
StyledText(
  'hello',
  style: TextStyler().reverse(), // Displays "olleh"
)

Number directive example

/// Directive that negates a number
final class NegateNumberDirective extends NumberDirective {
  const NegateNumberDirective();

  @override
  num apply(num value) => -value;

  @override
  String get key => 'number_negate';

  @override
  bool operator ==(Object other) =>
      identical(this, other) || other is NegateNumberDirective;

  @override
  int get hashCode => key.hashCode;
}

// Expose it as a chainable method on Prop
extension NegateNumberPropExt<T extends num> on Prop<T> {
  Prop<num> negate() {
    return directives([const NegateNumberDirective()]);
  }
}

Best Practices

  • Text directives for consistent formatting — use .uppercase() or .titlecase() instead of transforming strings before passing them in, so the transformation survives style merging
  • Number directives for proportional sizing — pair with tokens (e.g. Prop.token($base).multiply(2)) to keep ratios in the style rather than scattered across your code
  • Color directives for visual states — .withOpacity(0.5) on a disabled variant keeps the color relationship intact when the base color changes
  • Keep directives simple — a directive should do one thing. If you need complex logic, compute the value outside the style and pass it in

See Also

  • Styling — core Styler pattern and fluent chaining
  • Design Tokens — tokens work with directives via Prop.token()
  • Widget Modifiers — widget wrappers (modifiers transform widgets, directives transform values)
View page source on GitHub