Skip to Content
DocsGuidesDirectives

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( TextStyler(textDirectives: [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)