Architecture

Three threads, zero entanglement. How the system stays modular at every layer.

The Three Threads

The design system separates concerns into three independent threads. Each thread can evolve without affecting the others. V1 ships threads 1 and 2; thread 3 is deferred to V2.

Tokens (Aesthetics)

JSON values become CSS custom properties. Framework-agnostic. Themes override these. Every visual value in atom CSS comes from a var() token reference — zero hardcoded values.

Components (Structure)

Accessible HTML primitives. React implementation. Emit semantic DOM, ARIA attributes, data-* hooks, and .tcn-* classes. No CSS, no visual logic, no animation.

Interaction (Motion)

Transitions, transforms, animations, easing, gestures. Deferred to V2. This separation means V1 components work perfectly — they just change state instantly.

The Token Cascade

Tokens flow through three tiers with strict referencing direction: raw → alias → system. Each tier adds semantic meaning.

Raw Tokens

Primitive values with no semantic meaning. Color scales, sizing steps, font stacks. These are the building blocks.

raw/color.json
{
  "color": {
    "blue": {
      "500": { "value": "#3b82f6" },
      "600": { "value": "#2563eb" }
    },
    "neutral": {
      "0": { "value": "#ffffff" },
      "900": { "value": "#171717" }
    }
  }
}

Alias Tokens

Semantic roles that reference raw values. "Primary" maps to neutral.900 by default, but a theme can remap it to any raw value.

alias/color.json
{
  "color": {
    "primary": { "value": "{color.neutral.900}" },
    "on-primary": { "value": "{color.white}" },
    "surface": {
      "default": { "value": "{color.neutral.0}" }
    },
    "on-surface": {
      "default": { "value": "{color.neutral.900}" }
    }
  }
}

System Tokens

Component-specific tokens that reference aliases. This is where the surface/on-surface convention maps to specific components.

system/button.json
{
  "button": {
    "primary": {
      "surface": {
        "default": { "value": "{color.primary}" },
        "hover": { "value": "{color.primary-hover}" }
      },
      "on-surface": {
        "default": { "value": "{color.on-primary}" }
      }
    }
  }
}

Component Contract Model

Components emit structure and accessibility. They render semantic HTML elements, ARIA attributes, data-* state hooks, and .tcn-* class names. The CSS lives in the tokens package as atom CSS that maps these hooks to token values.

tsx
// Component renders structure + ARIA (no CSS)
<button
  class="tcn-button tcn-button-primary"
  data-size="md"
  data-loading="false"
  aria-busy="false"
>
  Click me
</button>

/* Atom CSS maps structure to tokens (no component logic) */
.tcn-button-primary {
  background: var(--button-primary-surface-default);
  color: var(--button-primary-on-surface-default);
  border-radius: var(--button-radius);
}

This separation means components can be ported to any framework (Vue, Flutter) — the visual layer is pure CSS custom properties.

Why Interaction Is Separate

Motion is the third axis. Transitions, transforms for hover effects, animations, and gesture handling all live in their own thread. In V1, state changes happen instantly — a button goes from default to hover with no transition. This is intentional: it proves the system works without motion, and V2 can layer motion on top without touching component or token code.

The only transforms allowed in V1 are structural: a toggle thumb's translateX to move between checked positions, or a checkbox chevron's rotate. These aren't motion — they're positional state.

Monorepo Structure

text
packages/
  tokens/        → Style Dictionary config + JSON tokens + atom CSS
  core/          → React primitives (structure + accessibility)
  interactions/  → Motion thread (V2)
  patterns/      → 36 theme-agnostic layout patterns

Build order: tokens → core → patterns. Turborepo manages the dependency graph. The documentation site and theme configurator live in separate repositories and consume the published npm packages.