Skip to Content
DocsMixGuidesDesign Tokens

Design Tokens

When you hardcode Colors.blue or EdgeInsets.all(16) across your app, changing your brand color or spacing scale means hunting through dozens of files. Design tokens solve this: you define a named value once, provide it at the top of your widget tree, and every style that references it updates automatically — including when you switch themes.

Mix provides built-in support for tokens through MixToken and MixScope.

Getting Started

Using a token takes three steps: declare it, provide a value, and reference it in a style.

// 1. Declare the token (the $ prefix is a naming convention to distinguish tokens from regular variables) final $primary = ColorToken('primary'); // 2. Provide the token value via MixScope MixScope( colors: { $primary: Colors.lightBlue, }, child: MyApp(), ); // 3. Reference the token in a style — call() resolves it from MixScope at build time final style = BoxStyler() .color($primary()) .size(100, 100);

MixScope

MixScope is the widget that provides token values to all its descendants. It works like Flutter’s Theme and ThemeData pattern — place it near the top of your tree and every descendant can resolve tokens from it.

MixScope( colors: { $primary: Colors.blue, $background: Colors.white, }, spaces: { $spacingMd: 16.0, }, radii: { $radiusMd: Radius.circular(8), }, child: MaterialApp(home: MyHomePage()), );

Nested MixScope widgets override token values for their subtree. The preview below shows an outer scope providing blue, and an inner scope overriding the same token to red for a child widget:

Resolving preview metadata...

Built-in Token Types

Mix provides token types for common styling needs:

Token TypeValue TypeUse Case
ColorTokenColorColors and backgrounds
SpaceTokendoubleSpacing values (padding, margin)
DoubleTokendoubleAny numeric value
RadiusTokenRadiusBorder radii
TextStyleTokenTextStyleTypography styles
BorderSideTokenBorderSideBorder definitions
ShadowTokenList<Shadow>Text shadows
BoxShadowTokenList<BoxShadow>Box shadows
FontWeightTokenFontWeightFont weights
DurationTokenDurationAnimation durations
BreakpointTokenBreakpointResponsive breakpoints

Using Tokens in Styles

Mix offers two ways to reference a token. Both resolve to the same value — the difference is ergonomics.

The simplest approach. Call the token like a function to get a reference that resolves at build time:

final style = BoxStyler() .color($primary()) .padding(.all($spacingMd()));

Prop.token (for directives)

Use Prop.token() with .create() when you need to chain directives on a token value — for example, doubling a spacing token:

final style = BoxStyler.create( width: Prop.token($baseSize).multiply(2), padding: Prop.token($spacingMd), );

For most styles, use the call syntax. Reach for Prop.token() only when you need directives like .multiply(), .add(), or .clamp().

Theme Switching

Tokens make theme switching straightforward: define two maps and swap them in MixScope.

final $background = ColorToken('background'); final $foreground = ColorToken('foreground'); final lightColors = { $background: Colors.white, $foreground: Colors.black, }; final darkColors = { $background: Colors.grey[900]!, $foreground: Colors.white, }; // Swap themes by toggling the map MixScope( colors: isDark ? darkColors : lightColors, child: MyApp(), );

Every style referencing $background or $foreground updates automatically when the map changes.

Resolving preview metadata...

Context Tokens

Sometimes a token’s value comes straight from the surrounding Flutter context — Theme.of(context).colorScheme.primary, the current MediaQuery, or a Localizations lookup. Wiring those through MixScope means recomputing the value at the right spot in the tree and re-providing it whenever it changes.

ContextToken<T> removes that step. You give it a resolver function that reads from BuildContext, and it computes its value at resolution time — no MixScope required.

// Declare once — the resolver runs against the consuming widget's context final $primary = ContextToken<Color>( (context) => Theme.of(context).colorScheme.primary, ); // Use it like any other token — no MixScope needed final style = BoxStyler().color($primary()); Box(style: style); // resolves to the ambient Theme's primary color

Because the value flows through the same token-reference pipeline as every other token, it keeps exact fluent-chain ordering:

BoxStyler().color($primary()).color(Colors.red); // red wins BoxStyler().color(Colors.red).color($primary()); // primary wins

Overriding a context token

A matching entry in MixScope still wins, so the resolver acts as a context-derived default that any scope can override:

MixScope( tokens: {$primary: Colors.green}, // overrides the resolver for this subtree child: Box(style: BoxStyler().color($primary())), );

ContextToken equality is based on the resolver’s identity, not a name. Two tokens built from the same top-level or static function are equal; two built from look-alike inline closures are not. Declare context tokens as top-level or static finals so overrides and merges match the token you expect.

Context tokens vs. scope tokens

Scope token (ColorToken, …)ContextToken
Value sourceMixScope mapsResolver reading BuildContext
Requires MixScope?YesNo (scope override is optional)
Best forDesign-system values you swap by themeValues derived from Flutter context (Theme, MediaQuery, …)

Resolving Tokens Programmatically

You can resolve tokens outside of styles using BuildContext. This is useful when you need a token value in non-Mix code, such as a plain Flutter widget:

@override Widget build(BuildContext context) { final primaryColor = $primary.resolve(context); final spacing = $spacingMd.resolve(context); return Container( color: primaryColor, padding: .all(spacing), child: Text('Resolved tokens'), ); }

Best Practices

  • Use the $ prefix for token variables ($primary, $spacingMd) — it makes tokens visually distinct from regular values at a glance
  • Use hierarchical names for the token string identifier: ColorToken('color.primary'), SpaceToken('spacing.md')
  • Group related tokens in separate files (tokens/colors.dart, tokens/spacing.dart) to keep declarations organized
  • Prefer tokens over hardcoded values — even if you only have one theme today, tokens make future changes a single-line edit instead of a find-and-replace

Common Pitfalls

Using $token() outside of Mix APIs. Calling a token (e.g. $primary()) returns a placeholder value, not the real resolved value. Mix replaces this placeholder internally when the style is resolved. If you pass $primary() to a plain Flutter widget like Container(color: $primary()), you’ll get the placeholder — not your actual color. Use $token.resolve(context) instead:

// Wrong — $primary() is a placeholder, not a real Color Container(color: $primary()) // Correct — resolve gives you the actual value from MixScope Container(color: $primary.resolve(context))

See Also

  • Styling — using tokens inside styler chains
  • DirectivesProp.token() with .multiply(), .add(), and other transforms
  • Creating Custom Tokens — building your own token types for values not covered by built-ins
  • Theming Tutorial — step-by-step guide to building a full themed app
Last updated on