Underlith logo Underlith

Governance

Governance is not bureaucracy. It is the agreement that design decisions are made once, in one place, and inherited everywhere — by components, products, and agents alike.

Core Principles

Three Levels of Governance

Level 1 — Underlith core

Base primitives: space, type, radius, motion, opacity, breakpoints, elevation, status.

Changes here affect every consumer. Treat as infrastructure — stable, slow-moving, semver-strict. No org-specific values live here.

Level 2 — Brand package (@your-org/tokens)

Your org's brand tokens and semantic aliases. Generated by underlith brand init.

This is the contract between design intent and every product your org builds. Changes here affect every surface. Publish a new version and every project updates on npm update @your-org/tokens.

Level 3 — Product consumers

Each product — web app, mobile, admin, website, AI agent — installs @your-org/tokens and inherits the contract. Products do not define colors. Products do not override tokens locally. If a product needs a variant, the variant is a new token, not a local override.

Underlith core       →    @your-org/tokens     →    Every product
(slow, stable)            (your cadence)            (npm update)
(primitives only)         (brand contract)          (components, agents)

Roles and Responsibilities

Change Process

Underlith core changes

  1. Open a PR with description, category, and assigned owner
  2. Run automated validations — schema, breaking-change detection, token-aware lint
  3. Review and approval by category owner and design team when applicable
  4. Merge and release — publish @mikaelcarrara/underlith
  5. Downstream brand packages update on their own schedule

Brand package changes

  1. Update token values in underlith.tokens.css or re-run underlith brand init
  2. Review with design team — validate semantic intent, not just the value
  3. Bump version following semver
  4. Publish — npm publish --access public
  5. Every product updates on npm update @your-org/tokens

Adding new tokens

New tokens start as a proposal: name, semantic intent, initial value, and which products consume it. A token without a clear consumer is not ready to ship.

Naming rules:

Deprecation policy

Mark tokens as deprecated in files and changelog. Maintain compatibility for at least two minor releases before removal. Removal requires a major version bump.

Token Drift

Token drift happens when a component defines a color, spacing, or typography value outside the token system — via a literal value or a local CSS variable not mapped to @your-org/tokens.

Drift is the primary sign that governance is failing.

What drift looks like

/* ❌ Drift — literal value in a component */
.badge {
  background-color: #d3fd54;
  color: #000;
}

/* ✅ Governed — token alias */
.badge {
  background-color: var(--ul-color-primary);
  color: var(--ul-color-primary-foreground);
}

Automated drift detection

CI runs a lint job that scans component files for literal color values, hardcoded spacing, and CSS variables not prefixed with --ul-. Any match fails the build.

The lint:tokens-check script scans for:

# .github/workflows/token-drift.yml
name: Token Drift Check

on:
  pull_request:
    paths:
      - 'src/**'
      - 'web/**'
      - 'styles/**'

jobs:
  drift:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run lint:tokens-check

CI and Automation

For the full automation narrative, examples and token‑aware prompts, see AI & Agents. This .html page tells the story; the .md files remain canonical for rules and policies.

Job Purpose
build:tokens Generate CSS from canonical source
lint:tokens Validate schema and detect breaking changes
lint:tokens-check Detect hardcoded values and unmapped variables in components
visual-regression Ensure replacements do not alter appearance
test:contrast Validate status and brand tokens meet WCAG AA contrast minimums
audit-log Record generations and refactors for review
name: Tokens CI

on:
  pull_request:
    paths:
      - 'src/tokens/**'
      - 'styles/**'
      - 'package.json'

jobs:
  build-tokens:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build:tokens
      - run: npm run test:tokens

  drift-check:
    runs-on: ubuntu-latest
    needs: build-tokens
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run lint:tokens-check

  contrast-check:
    runs-on: ubuntu-latest
    needs: build-tokens
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run test:contrast

  visual-regression:
    runs-on: ubuntu-latest
    needs: drift-check
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run visual-regression

Release Artifacts

Artifact Description
underlith.tokens.css Canonical token definitions as CSS Custom Properties
underlith.css Tokens + base styles, main entry point
your-org.brand.css Brand layer, generated by underlith brand init
your-org.base.css Base layer, generated by underlith brand init
underlith.tokens.json Tokens in JSON — enables Figma sync, Style Dictionary (roadmap)
CHANGELOG.md Change history and deprecation instructions

Accessibility and Motion Policy

@media (prefers-reduced-motion: reduce) {
  :root {
    --ul-duration-nano:     0ms;
    --ul-duration-micro:    0ms;
    --ul-duration-fast:     0ms;
    --ul-duration-base:     0ms;
    --ul-duration-moderate: 0ms;
    --ul-duration-slow:     0ms;
    --ul-duration-glacial:  0ms;
    --ul-duration-epic:     0ms;
  }
  *, *::before, *::after {
    animation-duration:       0.01ms !important;
    animation-iteration-count: 1     !important;
    transition-duration:      0.01ms !important;
  }
}

Signs the Governance is Working

Signs Something Went Wrong

For ongoing token health and long-term ownership, see STEWARDSHIP.md.
For consumption rules and alias strategy, see Consumption strategies.