Skip to content

Design System Architecture Rationale

Simeon Simeonoff edited this page Jan 21, 2026 · 1 revision

Design System Architecture: Rationale and Justification

Contents

  1. Executive Summary
  2. Problem Statement
  3. Constraints That Shaped Our Decisions
  4. Our Approach: Explicit Design Tokens
  5. Industry Comparison
  6. Addressing Concerns
  7. Tradeoffs Acknowledged
  8. Component Customization Philosophy
  9. Future Considerations
  10. Decision Summary

Executive Summary

This document explains the architectural decisions behind our design token system and component customization approach. We chose explicit, granular design tokens over computed or minimal token strategies. This decision prioritizes implementation clarity and cross-platform consistency over reduced token count.

Our constraints—Shadow DOM encapsulation, four distinct themes (Material, Bootstrap, Fluent, Indigo), two framework implementations (Angular and Web Components), and a small team—require an approach where design intent is unambiguous. When a token exists with a value, implementers use it. There is no guessing, no tribal knowledge, and no room for drift between platforms.

This approach requires more upfront work from designers but eliminates a class of bugs that are expensive to find and fix: visual inconsistencies between Angular and Web Components implementations that stem from ambiguous design specifications.


Problem Statement

The Handoff Gap

Design systems bridge two worlds: design tools (Figma) and code (CSS, Sass, component frameworks). The transition between these worlds—the "handoff"—is where ambiguity creeps in.

Consider a simple question: What color should a button's icon be when the button is hovered?

Without explicit specification, an implementer must:

  1. Check if it differs from the idle state (often undocumented)
  2. Check if it differs from the button's text color (sometimes yes, sometimes no)
  3. Check if this varies by theme (Material might differ from Fluent)
  4. Make a judgment call if none of the above is clear

Multiply this by dozens of components, four themes, and two framework implementations. Each ambiguous decision is an opportunity for inconsistency.

The Cost of Ambiguity

When design intent is ambiguous:

Stage Cost
Implementation Developer time spent researching, asking questions, making assumptions
Review Designer time spent checking implementations against intent
Bug Discovery Often late—discovered when comparing Angular vs. Web Components or switching themes
Bug Fixing Requires coordination between design and development to determine correct value
Regression Risk Fixes in one place may not propagate to the other framework

These costs compound. A 5-minute question during implementation becomes a 2-hour investigation months later when a customer reports that buttons look different in Angular vs. Web Components.

The Multi-Theme, Multi-Platform Challenge

Our design system supports:

  • 4 Themes: Material, Bootstrap, Fluent, and Indigo—each with distinct visual languages
  • 2 Frameworks: Angular (using standard CSS encapsulation) and Web Components (using Shadow DOM)
  • Light and Dark Modes: Each theme supports both, doubling the visual states

This matrix creates complexity:

4 themes × 2 frameworks × 2 modes = 16 combinations to keep consistent

A pattern that's true in Material ("disabled buttons always use gray-200") may not be true in Fluent ("disabled buttons use theme-specific disabled colors"). Without explicit tokens per theme, implementers must memorize or document these exceptions—and hope both Angular and Web Components teams remember them identically.


Constraints That Shaped Our Decisions

Shadow DOM and Component Encapsulation

All Ignite UI Web Components use Shadow DOM for style encapsulation. This provides benefits (style isolation, predictable rendering) but imposes a hard constraint:

If a style isn't exposed as a CSS custom property, consumers cannot customize it.

/* This does NOT work with Shadow DOM */
my-button .internal-icon {
  color: red; /* Blocked by shadow boundary */
}

/* This DOES work—if the component exposes it */
my-button {
  --button-icon-foreground-hover: red; /* Pierces shadow boundary */
}

This means every customizable aspect of a component must be explicitly exposed as a CSS custom property. We cannot rely on consumers using CSS selectors to override internal styles—the shadow boundary prevents it.

Implication: If we want button/icon/foreground/hover to be customizable, a CSS custom property must exist for it. The token in Figma maps directly to the CSS custom property in code.

Cross-Platform Parity Requirements

Customers expect Ignite UI components to look identical whether they're using Angular, Web Components, React, or Blazor. This is a fundamental product promise.

Achieving parity requires:

  1. Same design tokens used by both implementations
  2. Same token values for each theme
  3. Same interpretation of what each token means

If Angular computes hover = darken(idle, 10%) but Web Components computes hover = darken(idle, 8%), we have visual drift. Explicit tokens with explicit values eliminate this entirely—both frameworks read the same value from the same source.

Four Distinct Design Themes

Material, Bootstrap, Fluent, and Indigo are not just color variations—they have different design rules:

Aspect Material Bootstrap Fluent Indigo
Border radius 4px standard Varies by component 2px standard 3px standard
Disabled treatment Opacity-based Color-based Color-based Opacity + color
Focus indication Ripple + ring Ring only Thick outline/underline Ring
State color shifts Subtle Moderate Minimal Pronounced

A rule like "foreground color doesn't change on hover" might be true for Fluent but false for Indigo. Without explicit tokens per theme, implementers must maintain mental models of four different design languages.

Team Size and Resources

We operate with:

  • A small team of designers responsible for Figma component kits and token definitions
  • A small development team supporting both Angular and Web Components

This constraint affects our options:

Approach Designer Work Developer Work Viable for Us?
Minimal tokens + computation Low High (implement computation, document rules, debug drift) Risky
Explicit tokens Higher upfront Low (read token, apply value) Yes
Custom tooling to reduce token work Medium High (build and maintain tools) Not currently

With limited development resources, we cannot afford to build sophisticated tooling to reduce designer token workload. We also cannot afford the debugging time when computed values drift between platforms.

The explicit token approach front-loads work onto designers but reduces ongoing development burden and bug surface.


Our Approach: Explicit Design Tokens

What This Means in Practice

For each component, we define tokens for:

  • Each themeable property (background, foreground, border color, etc.)
  • Each interactive state (idle, hover, pressed, focused, disabled)
  • Each distinct part (icon, label, prefix, suffix—when they differ from the root)

Example for a Button component:

button/background/idle
button/background/hover
button/background/pressed
button/background/focused
button/background/disabled
button/foreground/idle
button/foreground/disabled
button/border/color/idle
button/border/color/focused
button/border/radius
button/border/width
...

Each token maps to a CSS custom property:

--button-background-idle: #2D6AE3;
--button-background-hover: #1E5BBF;
--button-foreground-idle: #FFFFFF;

Why Explicit Over Computed

The alternative to explicit tokens is computed values—deriving state variations from base values:

/* Computed approach (NOT what we do) */
--button-background-idle: #2D6AE3;
--button-background-hover: color-mix(in srgb, var(--button-background-idle) 90%, black);

This reduces token count but introduces problems:

Problem Impact
Platform sync Computation must be identical in Angular and Web Components
Design accuracy Computed colors may not match designer intent exactly
Theme variation Some themes may need different computation rules
Debugging "Why does hover look wrong?" requires understanding the formula
Figma disconnect Figma cannot compute—creates mismatch between design and code

Explicit tokens eliminate these problems. The value in Figma is the value in code. No computation, no interpretation, no drift.

The Role of Aliasing

Explicit tokens do not mean explicit unique values for every token. Figma supports variable aliasing—one variable referencing another.

A typical Button token structure with aliasing:

button/background/idle = {colors.primary.500}        ← references primitive
button/background/hover = {colors.primary.600}       ← references primitive
button/foreground/idle = {colors.white}              ← references primitive
button/foreground/hover = {button/foreground/idle}   ← alias to idle (same value)
button/foreground/pressed = {button/foreground/idle} ← alias to idle (same value)
button/icon/foreground/idle = {button/foreground/idle} ← alias (icon matches text)

In this example:

  • 3 unique color decisions (primary.500, primary.600, white)
  • 6+ tokens that alias to those decisions
  • Full explicitness in the structure, minimal decision-making work

When the export preserves aliases (which ours does), changing colors.primary.500 updates all referencing tokens automatically.


Industry Comparison

How Major Design Systems Handle This

We researched how major design systems (Carbon/IBM, Spectrum/Adobe, Fluent/Microsoft, Polaris/Shopify, Lightning/Salesforce) approach component tokens.

System Component Tokens per Component Strategy
Carbon (IBM) ~15 Colors for variants × key states only; sizes/spacing from global tokens
Spectrum (Adobe) ~12 base + variants 3-tier system (primitive → semantic → component); variants via CSS modifiers
Fluent UI (Microsoft) 0 (v9) No component tokens; components use semantic tokens directly
Polaris (Shopify) ~5 Only shadows are component-specific; everything else semantic
Our Approach ~30-100+ Full state × property matrix with aliasing

Why Their Approaches Don't Fit Our Constraints

Carbon's 15 Tokens

Carbon achieves low token count by:

  • Not exposing per-state tokens for non-color properties
  • Using global tokens for spacing, typography, borders
  • Computing state variations in CSS

Why this doesn't fit us: Carbon primarily targets React (no Shadow DOM concerns). They also control both design and development internally—ambiguity is resolved through direct communication, not explicit tokens.

Spectrum's 3-Tier System

Spectrum uses:

  • Primitive tokens (raw values)
  • Semantic tokens (purpose-based)
  • Component tokens (limited, mostly references)

State variations are handled via CSS modifiers, not separate tokens.

Why this doesn't fit us: Spectrum Web Components do expose CSS custom properties for customization, but they control the design language—one theme, not four. They can define computation rules that work for their single visual language.

Fluent's Zero Component Tokens

Fluent UI v9 removed component tokens entirely. Components reference semantic tokens directly.

Why this doesn't fit us: Fluent UI is Microsoft's design system for Microsoft's visual language only. They don't need to support Material, Bootstrap, or Indigo interpretations of what a button should look like.

Polaris's Semantic-Only Approach

Polaris defines semantic tokens (color-bg-surface-hover) that multiple components share.

Why this doesn't fit us: Polaris supports one theme (Shopify's brand). The semantic tokens encode one set of design decisions. We need four.

What We Learned From Their Patterns

Despite different approaches, all major systems share principles we adopted:

  1. Primitive → Semantic → Component hierarchy: Raw values feed semantic meanings feed component usage
  2. Aliasing reduces work: Most component tokens reference other tokens, not unique values
  3. Theme variations are mode-based: Light/dark handled via Figma modes or CSS selectors, not token name duplication
  4. Documentation is essential: Every system documents expected inheritance and usage patterns

What distinguishes our situation:

  • Multi-theme requirement: Most systems support one visual language; we support four
  • Multi-platform requirement: Most systems target one framework; we target two with strict parity
  • Shadow DOM universality: Some systems use Shadow DOM selectively; all our Web Components use it

Addressing Concerns

Concern: "Too Many Tokens"

The perception: "We need to create hundreds of tokens per component—that's overwhelming."

The reality: Tokens ≠ unique decisions. Most tokens are aliases.

Consider a Button with ~35 tokens:

Category Unique Values Aliased Tokens
Background colors 4-5 (idle, hover, pressed, focused, disabled) 0
Foreground colors 2 (idle, disabled) 3 (hover, pressed, focused alias to idle)
Icon foreground 0 5 (all alias to foreground equivalents)
Label foreground 0 5 (all alias to foreground equivalents)
Border colors 2 (idle, focused) 3 (hover, pressed, disabled alias or transparent)
Border radius/width 2 0
Spacing/gap 3 0

Actual design decisions: ~15-18 Tokens created: ~35 Tokens that are aliases: ~17-20 (roughly half)

The structure is explicit (every state has a token), but the work is not (most tokens point to other tokens).

Concern: "Designer Workload"

The concern: "Designers have to create and maintain all these tokens—that's a lot of work."

Response—Initial Setup: Yes, initial setup requires creating the token structure. This is a one-time cost per component. For a centralized team of a few designers, this is finite and plannable work.

The work is also structured and repeatable:

  1. Create background tokens for each state → reference primitives
  2. Create foreground tokens → reference primitives for idle/disabled, alias the rest
  3. Create part tokens (icon, label) → alias to parent foreground
  4. Create border tokens → reference primitives or alias as appropriate

Response—Ongoing Maintenance:

After initial setup, the typical workflow is:

  1. Change primitive color (e.g., colors.primary.500)
  2. All referencing component tokens update automatically via aliasing

Designers rarely need to touch individual component tokens unless intentionally changing a specific state's behavior.

Concern: "Maintenance Burden"

The concern: "When something changes, we have to update tokens everywhere."

Response: Aliasing handles this.

If button/background/idle references {colors.primary.500}, and the design team decides primary.500 should be a different blue:

  • Change colors.primary.500 in the primitives
  • All buttons (and other components referencing it) update automatically
  • No per-component token editing required

The explicit token structure does not mean explicit maintenance—the alias chains do the propagation.


Trade-offs Acknowledged

We made a deliberate trade-off. Here it is, stated plainly:

We Chose Over Because
More tokens Fewer tokens Eliminates implementation ambiguity
Higher designer setup cost Lower designer setup cost One-time cost vs. ongoing debugging cost
Explicit values Computed values Ensures Angular/WC parity without sync risk
Full state coverage Selective state coverage Shadow DOM requires explicit exposure for customization

What we gained:

  • Implementation clarity: Token exists → use it
  • Cross-platform parity: Same tokens, same values, same result
  • Theme flexibility: Each theme defines its own values independently
  • Debugging simplicity: Wrong color? Check the token value. No computation to trace.

What we accepted:

  • Designer workload for initial token creation
  • More Figma variables to organize and navigate
  • Potential perception of "over-engineering"

We believe this trade-off is correct for our constraints. We revisit this assessment as those constraints evolve.


Component Customization Philosophy

The Theming Contract

Our design system makes a promise to consumers:

Every visual aspect that can vary by theme is exposed as a CSS custom property that you can override.

This is the "theming contract" — the explicit surface area of customization we support.

What's inside the contract:

  • Colors (backgrounds, foregrounds, borders) for all states
  • Sizing (border radius, border width)
  • Spacing (padding, gap)
  • Shadows and elevations

What's outside the contract:

  • Internal layout structure
  • Animation implementation details
  • State machine logic
  • Accessibility behavior

What We Expose vs. What We Encapsulate

Exposed (Customizable) Encapsulated (Internal)
--button-background-hover How hover state is detected
--button-border-radius Which CSS properties use the radius
--button-padding-inline Flexbox vs. grid layout choice
--button-foreground-idle ARIA attribute management

The tokens define what the component looks like. The implementation defines how it works.

Shadow DOM and Styling Boundaries

With Shadow DOM, the styling boundary is clear:

┌─────────────────────────────────────────────────────────────────┐
│ Light DOM (Consumer's World)                                    │
│                                                                 │
│   my-button {                                                   │
│     --button-background-hover: red;  ← Consumer CAN set this    │
│   }                                                             │
│                                                                 │
├─────────────────────────────────────────────────────────────────┤
│ Shadow DOM (Component's World)                                  │
│                                                                 │
│   .internal-button:hover {                                      │
│     background: var(--button-background-hover);  ← Uses token   │
│   }                                                             │
│                                                                 │
│   .internal-icon {                                              │
│     /* Consumer CANNOT directly style this */                   │
│     color: var(--button-icon-foreground-idle);  ← Must use token│
│   }                                                             │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

If we don't expose a token, consumers can only customize that aspect via parts and explicit assignment of each CSS style property separately. This is why our token list is comprehensive — it defines the complete customization surface.


Future Considerations

Potential Tooling Improvements

If resources allow, future tooling could reduce designer workload:

  • Token templates: Pre-created Figma variable structures for common component patterns
  • Alias suggestions: Tooling that suggests "this token should probably alias to X"
  • Validation: Automated checks for missing states or non-standard naming

These are enhancements, not requirements. The system works without them.

Token Surface Reduction

If evidence shows certain tokens are never customized (always aliased, never overridden by consumers), we may consider:

  • Removing those tokens from the public API
  • Hardcoding those values internally
  • Documenting them as "internal, not supported for customization"

This would be a data-driven decision based on actual usage patterns.

Extension to Non-Color Tokens

Current focus is color tokens. Future phases may add:

  • Size tokens: Component dimensions, icon sizes
  • Spacing tokens: Padding, margin, gap values
  • Shadow tokens: Elevation and depth
  • Motion tokens: Animation duration and easing

Each category will follow the same principles: explicit, aliased where appropriate, and mapped to CSS custom properties.

Criteria for Architectural Changes

We would revisit this architecture if:

  1. Constraints change: Shadow DOM is no longer universal, or we drop themes
  2. Evidence accumulates: Data shows most tokens are never customized
  3. Tooling becomes available: Automation makes explicit tokens unnecessary
  4. Team feedback: Sustained, specific feedback that the approach isn't working

Absent these triggers, the architecture remains as documented.


Decision Summary

Decision Choice Rationale
Token granularity Explicit per state/part Shadow DOM requires exposure; eliminates implementation ambiguity
Computation vs. explicit Explicit values Ensures Angular/WC parity; matches Figma (no computation there)
Designer workflow Aliasing within explicit structure Reduces decisions while maintaining explicitness
Theme handling Per-theme token values via Figma modes Four themes have different rules; can't assume patterns transfer
Customization boundary CSS custom properties Shadow DOM constraint; clear contract with consumers

The core principle: When in doubt, be explicit. The cost of creating a token that turns out unnecessary is low. The cost of ambiguity that causes implementation drift across platforms and themes is high.


Appendix: Questions This Document Answers

For quick reference, this document addresses:

Question Section
"Why so many tokens?" Concern: "Too Many Tokens"
"Why not compute state colors?" Why Explicit Over Computed
"What do other design systems do?" Industry Comparison
"Why can't we do what Carbon/Spectrum does?" Why Their Approaches Don't Fit Our Constraints
"How does Shadow DOM affect this?" Shadow DOM and Component Encapsulation
"What's the actual designer workload?" The Role of Aliasing, Concern: "Designer Workload"
"What are we trading off?" Tradeoffs Acknowledged
"What can consumers customize?" Component Customization Philosophy
"Will this ever change?" Future Considerations

Clone this wiki locally