tokens

A constraint-checked design token substrate for Flutter.

One import. One access point (context.x). Zero design decisions in screen code.


The idea

Every value in your UI — colors, sizes, spacing, motion — carries a rule that keeps it valid. Contrast ratios are enforced at the constraint level. Spacing snaps to a 4dp rhythm. Type sizes follow a modular scale. Motion curves are always non-linear. You can't accidentally produce an inaccessible or off-rhythm interface through the normal API.


Setup

Wrap your app once:

import 'package:tokens/tokens.dart';

void main() {
  runApp(
    TokenScope(
      child: MyApp(),
    ),
  );
}

That's it. No extra configuration. Light mode, dark mode, and high-contrast resolve automatically from MediaQuery.


Usage

Access every token via context.x — anywhere in the tree:

// Colors (auto-adapt to light / dark)
context.x.surface        // background color
context.x.surfaceVariant // slightly elevated surface
context.x.onSurface      // primary text / icon color
context.x.onSurfaceMuted // secondary text
context.x.accent         // brand accent color
context.x.onAccent       // text/icon on top of accent
context.x.error          // error color
context.x.outline        // dividers, borders

// Typography (color included)
context.x.display   // 39dp · w700
context.x.headline  // 31dp · w600
context.x.title     // 25dp · w500
context.x.body      // 16dp · w400
context.x.label     // 13dp · w500
context.x.caption   // 10dp · muted

// Spacing (4dp rhythm)
context.x.xs  // 4
context.x.sm  // 8
context.x.md  // 16
context.x.lg  // 24
context.x.xl  // 32
context.x.xxl // 48

// Motion
context.x.press       // 120ms
context.x.exit        // 200ms
context.x.expand      // 280ms
context.x.enter       // 320ms
context.x.enterCurve  // easeOutCubic
context.x.exitCurve   // easeInCubic

// Elevation
context.x.flat   // 0
context.x.raised // 1
context.x.card   // 4
context.x.modal  // 8

A full screen — no raw values anywhere:

Padding(
  padding: EdgeInsets.all(context.x.md),
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text('Welcome', style: context.x.headline),
      SizedBox(height: context.x.sm),
      Text('This adapts to light and dark automatically.', style: context.x.body),
      SizedBox(height: context.x.lg),
      AnimatedContainer(
        duration: context.x.enter,
        curve: context.x.enterCurve,
        color: context.x.accent,
        padding: EdgeInsets.symmetric(
          horizontal: context.x.md,
          vertical: context.x.sm,
        ),
        child: Text('Get started', style: context.x.label.copyWith(color: context.x.onAccent)),
      ),
    ],
  ),
)

Custom brand color

Pass your brand color to TokenScope. In debug mode, an AssertionError fires if it fails WCAG AA (≥ 4.5:1 contrast) — before it ever ships.

TokenScope(
  palette: TokenPalette(
    accentLight: Color(0xFF9B1D20), // ≥ 4.5:1 on white, enforced
    accentDark:  Color(0xFFEF9A9A), // ≥ 4.5:1 on #121212, enforced
  ),
  child: MyApp(),
)

Contrast utility

final ratio  = contrastRatio(foreground, background); // double, e.g. 7.3
final passes = meetsWcagAA(foreground, background);   // bool

What this is not

  • Not a widget kit — it themes whatever widgets you already use.
  • Not a ThemeData replacement — coexists with Material without conflict.
  • Not a CLI or code generator.

License

MIT — see LICENSE.

const like = 'sample';

License

MIT — see LICENSE.

Libraries

tokens