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:
| Method | Description | Input | Output |
|---|---|---|---|
.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),
)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
| Method | Description | Example |
|---|---|---|
.multiply(factor) | Multiplies by a factor | Prop.value(10).multiply(2) → 20 |
.add(addend) | Adds a value | Prop.value(10).add(5) → 15 |
.subtract(value) | Subtracts a value | Prop.value(10).subtract(3) → 7 |
.divide(divisor) | Divides by a value | Prop.value(10).divide(2) → 5 |
.clamp(min, max) | Constrains between bounds | Prop.value(25).clamp(0, 20) → 20 |
.abs() | Returns absolute value | Prop.value(-10).abs() → 10 |
.round() | Rounds to nearest integer | Prop.value(15.7).round() → 16 |
.floor() | Rounds down | Prop.value(15.7).floor() → 15 |
.ceil() | Rounds up | Prop.value(15.3).ceil() → 16 |
.scale(ratio) | Alias for multiply | Prop.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 = 25Color Directives
Transform colors by adjusting opacity, brightness, saturation, and more. Color directives are called directly on color styler methods:
| Method | Description |
|---|---|
.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 logickey— 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)