Skip to content

Design Tokens

Kenny Mochizuki Escalona edited this page Aug 21, 2026 · 9 revisions

Design Tokens

A complete reference of all design tokens in layrz_ui. This is the source of truth for semantic color, typography, spacing, radius, shadow, border, and motion values.


Color Tokens

All colors are defined for light mode only. See decision D7 for details.

Access via context.theme.tokens.colors or context.tokenizer.colors.

Brand Color

Token Value Hex Usage
primary Deep navy blue (customizable) #001E60 Interactive elements, prominent actions, primary brand

Default: customizable via LayrzThemeData.light(primaryColor: ...).

Surface Colors

A numbered ramp of four surface steps for layered composition, from lightest (canvas) to darkest (deepest nesting).

Token Value Hex Usage
sf1 Lightest gray #FCFCFC Canvas/scaffold background, cards, dialogs, panels, overlays (elevation shadow provides separation)
sf2 Light gray 2 #F7F7F7 Nested containers, secondary elevations, popovers
sf3 Light gray 3 #F0F0F0 Deeper nesting, tertiary elevations
sf4 Darkest gray #E8E8E8 Deepest nesting surface color, maximum contrast (currently unused)

Foreground / Text Colors

Token Value Hex Usage
fg1 Very dark navy #1A1A2E Highest-contrast text (labels, body)
fg2 Dark gray #4A4A5A Secondary text, borders
fg3 Medium gray #9E9E9E Placeholders, disabled text, hints
fg4 Light gray #C4C4C4 Very subtle text, lowest contrast

Semantic Colors

Token Value Hex Usage
danger Red #E53935 Errors, destructive actions, critical alerts
success Green #43A047 Positive confirmations, valid input, good status
warning Orange #FB8C00 Cautions, non-critical alerts, warnings
info Blue #1E88E5 Informational and neutral alerts

Structural Colors

Token Value Hex Usage
contextual Medium gray #9E9E9E Neutral status, informational elements
divider Light gray #E0E0E0 Borders, dividers, separator lines
overlay Black @ 50% #00000080 Modal scrims, overlay backgrounds

Tonal Opacity

Token Value Usage
tonalOpacity 0.2 (20%) Alpha value applied to tonal/filledTonal fills to create visual distinction

Typography Tokens

Access via context.theme.tokens.typography or context.theme.textTheme.

layrz_ui defines five text styles — one per semantic role. All styles use Open Sans by default (customizable via font parameters in LayrzThemeData.light()). Font color is always fg1 (highest-contrast text). See decision D23 for the rationale on the five-style scale.

Display Style

Style Size Weight Usage
display 40 700 Hero text, splash screens, major section headers

Headline Style

Style Size Weight Usage
headline 24 600 Section headers (h2), subsection headers (h3)

Title Style

Style Size Weight Usage
title 20 600 Card titles, dialog headers, item titles, field labels

Body Style

Style Size Weight Usage
body 16 400 Default root text style. Paragraph text, UI copy, secondary text, captions

Label Style

Style Size Weight Usage
label 14 400 Button labels, form labels, tags, badges

Text Wrapping and Overflow

By default, text in layrz_ui wraps freely when space is available. Truncation (the three-dot ellipsis) is an explicit choice made by individual components when their geometry is fixed.

What this means for you:

  • Text in unlimited space wraps naturally — no special handling required
  • Fixed-height containers (badges, button labels, input fields) may truncate at the component level
  • If you want to force truncation, wrap the component or pass a maxLines parameter if the component exposes one
  • Page layout may change if text previously truncated silently — verify your layouts still fit after upgrade

See decision D51 for the full design rationale.


Spacing Tokens

Access via context.theme.tokens.spacing or context.tokenizer.spacingTokens.

All values are in logical pixels. Spacing uses five semantic levels with the value scale 4, 8, 16, 24, 32 — mirroring the radius and shadow elevation patterns for design system consistency.

Token Value Usage
sp1 4.0 Extra-small gaps, reduced spacing
sp2 8.0 Small gaps, standard spacing
sp3 16.0 Standard padding / margin
sp4 24.0 Large padding
sp5 32.0 Extra-large padding

Convenience Accessors

// Padding clarity — both are EdgeInsets.all(spN), identical in value
context.tokens.spacing.pd1 // EdgeInsets.all(4)
context.tokens.spacing.pd2 // EdgeInsets.all(8)
context.tokens.spacing.pd3 // EdgeInsets.all(16)
context.tokens.spacing.pd4 // EdgeInsets.all(24)
context.tokens.spacing.pd5 // EdgeInsets.all(32)

// Margin clarity — same values as pd*, different name for intent
context.tokens.spacing.mg1 // EdgeInsets.all(4)
context.tokens.spacing.mg2 // EdgeInsets.all(8)
context.tokens.spacing.mg3 // EdgeInsets.all(16)
context.tokens.spacing.mg4 // EdgeInsets.all(24)
context.tokens.spacing.mg5 // EdgeInsets.all(32)

All other convenience methods (base, margin, reducedMargin, padding, spacingSize, sizedBox) were removed in the refactor to enforce the five-level model.


Breakpoint Tokens

Access via context.theme.tokens.breakpoints or context.tokens.breakpoints.

Breakpoint tokens define the viewport width thresholds that determine which responsive band a layout is in. All values are in logical pixels. Each field represents the upper bound (exclusive) of the band below it:

Token Value (u) Band Range Usage
xs 600 xs: < 600 Mobile: phones, small tablets
sm 960 sm: 600–959 Tablets (portrait)
md 1264 md: 960–1263 Tablets (landscape), small desktops
lg 1904 lg: 1264–1903 Large desktops
(xl) xl: ≥ 1904 Extra-large displays (no token; defined by remaining space)

Themeable Breakpoints

Apps can override breakpoint thresholds by passing a custom theme to LayrzApp:

final customTheme = LayrzThemeData.light().copyWith(
  tokens: tokens.copyWith(
    breakpoints: LayrzBreakpointTokens(
      xs: 480.0,   // Custom mobile threshold
      sm: 900.0,   // Custom tablet threshold
      md: 1200.0,  // Custom desktop threshold
      lg: 1800.0,  // Custom large-desktop threshold
    ),
  ),
);

LayrzApp(
  theme: customTheme,
  // ...
)

All components that read breakpoints (LayrzRow, LayrzCol, responsive layouts) will follow the custom thresholds automatically.

Breakpoint Resolution

Use the bandAt(double width) method to resolve a viewport width to its breakpoint band:

final band = context.tokens.breakpoints.bandAt(500);
// Returns LayrzBreakpoint.xs (because 500 < 600)

final band = context.tokens.breakpoints.bandAt(1500);
// Returns LayrzBreakpoint.lg (because 1500 >= 1264 and < 1904)

Convenience getter: Use context.breakpoint to get the current breakpoint band based on the viewport width:

if (context.breakpoint == LayrzBreakpoint.xs) {
  // Mobile layout
} else if (context.breakpoint == LayrzBreakpoint.md) {
  // Tablet or desktop layout
}

Radius Tokens

Access via context.theme.tokens.radius or context.tokenizer.radiusTokens.

All values are in logical pixels. Radius uses five semantic levels with the value scale 4, 8, 16, 24, 32 — mirroring the spacing and shadow elevation patterns for design system consistency.

Token Value Usage
r1 4.0 Subtle rounding
r2 8.0 Standard corner rounding
r3 16.0 Medium rounding
r4 24.0 Large rounding
r5 32.0 Extra-large rounding
full 999.0 Pill shape (fully rounded)

Convenience Accessors

// BorderRadius at semantic levels — computed on-demand, not in copyWith/==/hashCode
context.tokens.radius.br1 // BorderRadius.circular(4)
context.tokens.radius.br2 // BorderRadius.circular(8)
context.tokens.radius.br3 // BorderRadius.circular(16)
context.tokens.radius.br4 // BorderRadius.circular(24)
context.tokens.radius.br5 // BorderRadius.circular(32)

Inner Radius Calculation

For nested container borders, compute the inner radius using:

context.tokens.radius.innerRadius(
  outerRadius: context.tokens.radius.r4,  // Outer: 24 logical pixels
  spacer: context.tokens.spacing.sp2,      // Spacing: 8 logical pixels
)
// Returns BorderRadius.circular(16) because inner = 24 − 8 = 16

Rule: The inner corner arc must share the outer arc's centre. The spacer represents the distance between the two arcs' centres; subtracting it from the outer radius gives the inner radius. If the result is negative (e.g., outerRadius: 4, spacer: 10), it clamps to 0, producing a pill shape.

All other convenience methods (base, borderRadius getter) were removed in the refactor to enforce the five-level model. Use br2 for the common case where you previously used borderRadius.


Shadow Tokens

Access via context.theme.tokens.shadow or context.tokenizer.shadowTokens.

Shadow tokens map elevation levels (0–5) to BoxShadow lists. The algorithm computes blur radius, opacity, and offset based on elevation to create visual hierarchy.

Elevation Algorithm

  • Opacity: Linear interpolation from 0.06 (elevation 0) to 0.12 (elevation 5)
  • Blur Radius: 3 × elevation + 2
  • Vertical Offset: elevation − 1 pixels downward

Elevation Getters

Token Elevation Usage
elevation1 1 Subtle elevation, cards
elevation2 2 Standard elevation, popovers
elevation3 3 Medium elevation, overlays
elevation4 4 High elevation, modal dialogs
elevation5 5 Highest elevation, top-most overlays

Example:

context.tokens.shadow.elevation2
// Returns: [BoxShadow(color: black@9%, blur: 8, offset: Offset(0, 1))]

Elevation Method

For custom elevation settings:

context.tokenizer.shadow(
  elevation: 2,           // 0–5 (required)
  radius: 12.0,           // Custom border radius (optional, defaults to base)
  color: Colors.white,    // Surface color (optional, defaults to surface)
  shadowColor: Colors.black, // Shadow color (optional)
  reverse: false,         // Flip offset for pressed state (optional)
  hideOnElevationZero: false, // Hide shadow at elevation 0 (optional)
)

Returns a BoxDecoration with:

  • The specified surface color and border radius
  • Computed box shadow for the elevation
  • At elevation 0: a 1-pixel outline border (unless hideOnElevationZero: true)

Example: Pressed Button State:

context.tokenizer.shadow(elevation: 2, reverse: true)
// Reverses the shadow offset for a "pressed" visual effect

Border Tokens

Access via context.theme.tokens.border.

Border tokens define stroke widths and pre-built BorderSide objects.

Token Value Usage
base 1.5 Default border width
stroke1 1.0 Thin stroke
stroke2 2.0 Medium stroke
stroke3 3.0 Thick stroke

Pre-built BorderSides

Property Value Usage
light BorderSide(color: divider, width: 1.0) Thin divider lines
normal BorderSide(color: divider, width: 2.0) Standard borders
thick BorderSide(color: divider, width: 3.0) Prominent borders

Example:

Container(
  decoration: BoxDecoration(
    border: Border(bottom: context.tokens.border.normal),
  ),
)

Motion Tokens

Access via context.theme.tokens.motion or context.tokenizer.motion.

Motion tokens define animation durations and easing curves.

Durations

Token Value Usage
dHover 100ms Hover state transitions
dPress 80ms Press/tap feedback
dTransition 200ms Component state changes
dPageTransition 250ms Page navigation
dDialog 300ms Dialog entrance/exit

Easing Curves

Token Value Usage
easing Curves.easeInOut Standard animations
easingEnter Curves.easeOut Entrance animations (fast start)
easingExit Curves.easeIn Exit animations (slow start)

Example:

AnimatedOpacity(
  duration: context.tokens.motion.dTransition,
  curve: context.tokens.motion.easing,
  opacity: isHovered ? 1.0 : 0.5,
  child: MyWidget(),
)

Application Constants

The following constants are exported from package:layrz_ui/layrz_ui.dart as default values for the token system:

Constant Value Hex Description
kPrimaryColor Deep navy blue #001E60 Layrz primary brand color (default tokens.colors.primary)
kHoverDuration Micro-interaction animation duration (100ms, default tokens.motion.dHover)
kPageTransitionDuration Page transition animation duration (250ms, default tokens.motion.dPageTransition)

These are convenience re-exports of the most-used defaults. For customization, pass overrides to LayrzThemeData.light() or LayrzApp:

LayrzApp(
  theme: LayrzThemeData.light(
    primaryColor: const Color(0xFF0077BE), // Override default brand color
  ),
  // ...
)

Accessing Tokens

Pattern 1: BuildContext Extensions (Recommended)

context.theme.tokens.colors.primary
context.tokens.colors.primary
context.tokenizer.primary

All three patterns are equivalent but offer different levels of brevity.

Pattern 2: Direct Access via LayrzTheme

final theme = LayrzTheme.of(context);
final primary = theme.tokens.colors.primary;

Pattern 3: Direct Access via LayrzTokens

final tokens = context.tokens;
final primary = tokens.colors.primary;

Customizing Tokens

Modify tokens via LayrzThemeData.copyWith() and LayrzTokens.copyWith():

final customTheme = theme.copyWith(
  tokens: theme.tokens.copyWith(
    colors: theme.tokens.colors.copyWith(
      primary: const Color(0xFF0077BE),
      danger: const Color(0xFFD32F2F),
    ),
    spacing: theme.tokens.spacing.copyWith(
      sp3: 18.0, // Increase standard padding
    ),
  ),
);

Then pass the custom theme to LayrzApp:

LayrzApp(
  theme: customTheme,
  // ...
)

Related Decisions


Last updated: 2026-08-13
Related pages: Theming, LayrzTokenizer, Getting-Started, Architecture

Clone this wiki locally