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
ThemeDatareplacement — coexists with Material without conflict. - Not a CLI or code generator.
License
MIT — see LICENSE.
const like = 'sample';
License
MIT — see LICENSE.