Token-based design system with themes, JSON import/export, and validation
dart pub add philiprehberger_design_tokensToken-based design system with themes, JSON import/export, and validation. Zero dependencies. Pure Dart.
Add to your pubspec.yaml:
dependencies:
philiprehberger_design_tokens: ^0.4.0
Then run:
dart pub get
import 'package:philiprehberger_design_tokens/design_tokens.dart';
final theme = Theme(
name: 'light',
colors: {
'primary': ColorToken.fromHex('#3366FF'),
'background': ColorToken.fromHex('#FFFFFF'),
},
spacings: {
'sm': SpacingToken(value: 8.0),
'md': SpacingToken(value: 16.0),
},
typographies: {
'body': TypographyToken(fontSize: 16.0, fontWeight: FontWeight.regular),
},
);
final color = theme.color('primary');
print(color!.toHex()); // #3366ff
final manager = ThemeManager();
manager.register(lightTheme);
manager.register(darkTheme);
manager.onChange((theme) => print('Now using: ${theme.name}'));
manager.switchTo('dark');
// Colors with hex parsing
final color = ColorToken.fromHex('#FF6633');
final rgba = ColorToken(red: 1.0, green: 0.4, blue: 0.2);
// Spacing
const spacing = SpacingToken(value: 16.0);
// Typography with font weight enum
const heading = TypographyToken(
fontSize: 32.0,
fontWeight: FontWeight.bold,
lineHeight: 1.2,
letterSpacing: -0.5,
);
// Shadows
final shadow = ShadowToken(
color: ColorToken.fromHex('#000000'),
radius: 8.0,
xOffset: 0.0,
yOffset: 4.0,
opacity: 0.15,
);
// Borders
final border = BorderToken(
width: 1.0,
color: ColorToken.fromHex('#E0E0E0'),
style: BorderStyle.dashed,
);
// Merge: other theme's tokens override matching keys
final merged = baseTheme.merging(overrideTheme);
// Extend: add tokens while keeping the original name
final extended = baseTheme.extending(
colors: {'accent': ColorToken.fromHex('#FF6633')},
);
// Register semantic aliases for existing token keys
final themed = theme.withAliases({
'brand': 'primary',
'small': 'sm',
'paragraph': 'body',
});
// Resolve by alias or direct name
final color = themed.resolveColor('brand'); // same as theme.color('primary')
final spacing = themed.resolveSpacing('small'); // same as theme.spacing('sm')
final typo = themed.resolveTypography('paragraph');
// Define breakpoint-dependent values
final spacing = ResponsiveToken<double>({
'mobile': 8.0,
'tablet': 16.0,
'desktop': 24.0,
});
print(spacing.resolve('tablet')); // 16.0
print(spacing.containsBreakpoint('mobile')); // true
print(spacing.breakpointNames); // [mobile, tablet, desktop]
// JSON round-trip
final json = spacing.toJson((v) => v);
final restored = ResponsiveToken.fromJson<double>(json, (v) => (v as num).toDouble());
final exporter = TokenExporter();
// Export to JSON map
final json = exporter.exportJson(theme);
// Import from JSON map
final restored = exporter.importJson(json);
// Serialize to bytes
final bytes = exporter.serialize(theme);
final fromBytes = exporter.deserialize(bytes);
All token constructors validate their inputs at construction time:
ColorToken -- RGBA channels must be between 0.0 and 1.0SpacingToken -- value must be non-negativeTypographyToken -- fontSize must be positive, lineHeight (if set) must be positiveBorderToken -- width must be non-negativeShadowToken -- radius must be non-negative, opacity must be between 0.0 and 1.0Invalid values throw RangeError.
ColorToken includes WCAG 2.1 contrast helpers for verifying color pairs:
final foreground = ColorToken.fromHex('#000000');
final background = ColorToken.fromHex('#FFFFFF');
// Relative luminance per WCAG 2.1
print(foreground.relativeLuminance); // 0.0
print(background.relativeLuminance); // 1.0
// Contrast ratio (1.0 .. 21.0)
print(foreground.contrastRatio(background)); // ~21.0
// AA: 4.5:1 normal text, 3:1 large text
foreground.meetsWcagAA(background); // true
foreground.meetsWcagAA(background, largeText: true); // true
// AAA: 7:1 normal text, 4.5:1 large text
foreground.meetsWcagAAA(background); // true
foreground.meetsWcagAAA(background, largeText: true); // true
final validator = TokenValidator();
final issues = validator.validate(
theme,
requiredColors: ['primary', 'background', 'error'],
requiredSpacing: ['sm', 'md', 'lg'],
requiredTypography: ['body', 'heading'],
requiredShadows: ['card'],
requiredBorders: ['default'],
);
for (final issue in issues) {
print('${issue.severity.name}: ${issue.message}');
}
| Class | Description |
|---|---|
ColorToken | RGBA color with hex parsing (fromHex, toHex) |
SpacingToken | Numeric spacing value |
TypographyToken | Font size, weight, line height, letter spacing |
ShadowToken | Shadow with color, radius, offsets, opacity |
BorderToken | Border with width, color, and style |
TokenAlias | Maps an alias name to an existing token key |
ResponsiveToken<T> | Breakpoint-dependent token values |
| Member | Description |
|---|---|
relativeLuminance | WCAG 2.1 relative luminance in [0.0, 1.0] |
contrastRatio(other) | WCAG 2.1 contrast ratio against another color (1.0..21.0) |
meetsWcagAA(other, {largeText}) | true if contrast meets WCAG AA (4.5 normal, 3.0 large) |
meetsWcagAAA(other, {largeText}) | true if contrast meets WCAG AAA (7.0 normal, 4.5 large) |
| Method | Description |
|---|---|
color(key) | Look up a color token |
spacing(key) | Look up a spacing token |
typography(key) | Look up a typography token |
shadow(key) | Look up a shadow token |
border(key) | Look up a border token |
merging(other) | Merge another theme (other overrides) |
extending(...) | Extend with additional tokens |
withAliases(aliases) | Return new theme with semantic aliases |
resolveColor(nameOrAlias) | Look up color by name or alias |
resolveSpacing(nameOrAlias) | Look up spacing by name or alias |
resolveTypography(nameOrAlias) | Look up typography by name or alias |
aliases | Registered aliases (unmodifiable map) |
| Method | Description |
|---|---|
resolve(breakpoint) | Get value for a breakpoint |
breakpointNames | List of all breakpoint keys |
containsBreakpoint(breakpoint) | Check if breakpoint exists |
toJson(serializer) | Serialize to JSON map |
fromJson(json, deserializer) | Deserialize from JSON map |
| Method | Description |
|---|---|
register(theme) | Register a theme |
switchTo(name) | Switch active theme |
activeTheme | Current active theme |
availableThemes | List of registered theme names |
onChange(callback) | Listen for theme changes |
| Method | Description |
|---|---|
exportJson(theme) | Export theme to JSON map |
importJson(json) | Import theme from JSON map |
serialize(theme) | Export to UTF-8 bytes |
deserialize(bytes) | Import from UTF-8 bytes |
| Method | Description |
|---|---|
validate(theme, ...) | Validate against required token names (colors, spacing, typography, shadows, borders) |
dart pub get
dart analyze --fatal-infos
dart test
If you find this project useful: