Skip to main content

Design tokens

Never hardcode values

No hardcoded values

Colour, spacing, radius, elevation, and typography values must always reference a design token. Hardcoded values break theming (light/dark, contrast modes) and will be rejected in review.

foo.dart
// ✅ Good — all values from tokens
Container(
padding: EdgeInsets.all(Zeta.of(context).spacing.medium),
decoration: BoxDecoration(
color: colors.surfaceDefault,
border: Border.all(color: colors.borderDefault),
borderRadius: context.rounded
? BorderRadius.circular(ZetaSpacing.minimum)
: BorderRadius.zero,
),
child: Text(label, style: ZetaTextStyles.bodyMedium),
)

// ❌ Bad — hardcoded values break theming
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFFFFFFF),
border: Border.all(color: const Color(0xFFCCCCCC)),
borderRadius: BorderRadius.circular(4),
),
child: Text(label, style: const TextStyle(fontSize: 16)),
)

Token namespaces

Tokens are accessed via Zeta.of(context) and related classes. Always use semantic tokens — raw values are for token definitions only.

Class / accessorPurposeExamples
colors.surface*Background fillssurfaceDefault, surfacePrimary, surfaceNegative
colors.main*Foreground / text / iconmainDefault, mainSubtle, mainDisabled, mainInverse
colors.border*Border colorsborderDefault, borderPrimary, borderNegative
colors.state*Interactive state fillsstateDefaultEnabled, stateDefaultHover, stateDefaultSelected
ZetaSpacing.*Padding, gap, marginZetaSpacing.minimum, ZetaSpacing.small, ZetaSpacing.large
ZetaTextStyles.*TypographyZetaTextStyles.bodyMedium, ZetaTextStyles.labelLarge
Use ZetaTextStyles not raw TextStyle

Text styles include the correct font weight, size, and line-height together. Using TextStyle(fontSize: ...) alone loses weight and line-height:

foo.dart
// ✅ Good
Text(label, style: ZetaTextStyles.bodyMedium)

// ❌ Bad — loses weight and line-height
Text(label, style: const TextStyle(fontSize: 14))

Stylesheet / theme setup

All colors come from Zeta.of(context).colors. This automatically resolves to the correct value for the current theme mode (light, dark, high-contrast light, high-contrast dark):

foo.dart
final colors = Zeta.of(context).colors;

Always derive border radius from the rounded property — never hardcode:

foo.dart
BorderRadius get _borderRadius => context.rounded
? BorderRadius.circular(ZetaSpacing.minimum)
: BorderRadius.zero;

For interaction states, use InkWell with ZetaColors.state* tokens:

foo.dart
InkWell(
onTap: onTap,
overlayColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.pressed)) return colors.stateDefaultSelected;
if (states.contains(WidgetState.hovered)) return colors.stateDefaultHover;
return null;
}),
borderRadius: _borderRadius,
child: ...,
)

Size variants should use a single switch expression — not scattered if/else blocks:

foo.dart
// ✅ Good
double get _height => switch (size) {
ZetaWidgetSize.small => ZetaSpacing.xl_6,
ZetaWidgetSize.medium => ZetaSpacing.xl_9,
ZetaWidgetSize.large => ZetaSpacing.xl_11,
};