---
title: Toolchain
source: 'https://academia.sh/en/courses/design-systems/toolchain'
course: 'Design Systems'
language: en
updated: '2026-08-19T05:19:54+00:00'
license: 'CC BY-SA 4.0'
---

# Toolchain

The single-direction source of truth between the design tool and code, normalizing the token export, eliminating false diffs, and classifying the remaining real deviation to arrive at a sync rate.

The previous lesson's scan stayed on the code side: it checked whether the system
component was used, but not whether the values it carries match the design library's.
A team may be using the system button while the button's fill color differs from the
design file's; coverage percentage counts this as one hundred percent.

The toolchain closes this gap. Its job is to **continuously verify** that the values in
the design tool and the values in code are the same. This lesson defines the chain's
direction, produces the diff report, and shows the normalization needed for the report
to become readable.

## The Direction of Truth

If a value is kept in two places, which one is true must be decided in advance. There
are three arrangements, and all three are single-direction:

- **The design tool is the source.** Values are defined in the design library,
  exported, and generated into code. Code is never edited by hand.
- **Code is the source.** Values are defined in a token file and imported into the
  design tool. The designer changes the value in the file, not in the tool.
- **There is a neutral source.** Values are defined in a file independent of both the
  tool and the code — a JSON document — and both the design tool and code are fed from
  this file.

A fourth option that looks available, **two-way sync**, does not work: if both sides
have write access, the same token changing on both at once produces a conflict with no
rule to resolve it — which value is newer does not tell you which is more correct.
This is why the first decision in building a toolchain is which direction it flows;
technical choices come after.

Once the direction is chosen, the chain's shape is the same: **export → transform →
target formats**. The transform step converts names to the target's convention, brings
values into the target's units, and computes derived tokens. There can be more than one
target format — CSS custom properties, mobile format files, and doc pages can all be
generated from the same source.

## Producing the Diff Report

Whichever direction is chosen, the two sides still need to be checked for sameness.
The real obstacle to this check is that the two sides **write the same value in a
different form**: the design tool writes a color in uppercase hex, code in lowercase;
the design tool writes a length in pixels, code as a root-relative unit.

The program below compares eighteen design tokens against eighteen code tokens in
three stages: raw, with only names normalized, and with both names and values
normalized.

```js
// toolchain.mjs — diff report between the design tool's token export and the code
// token definition

// The design tool's exported name-value pairs.
const DESIGN = {
  "Color/Surface/Primary": "#FFFFFF",
  "Color/Surface/Secondary": "#F7F7F8",
  "Color/Border/Default": "#B7BCC2",
  "Color/Text/Primary": "rgb(31, 35, 40)",
  "Color/Text/Secondary": "#5A6169",
  "Color/Action/Main": "#275EA5",
  "Color/Action/On Main": "#FFF",
  "Color/Action/Secondary": "#3B7BC8",
  "Spacing/Step 1": "4px",
  "Spacing/Step 2": "8px",
  "Spacing/Step 3": "12px",
  "Spacing/Step 4": "16px",
  "Typography/Body/Size": "16px",
  "Typography/Body/Line Height": "1.5",
  "Typography/Heading/Size": "24px",
  "Radius/Small": "4px",
  "Radius/Medium": "8px",
  "Shadow/Elevation 1": "0 1px 2px rgba(0, 0, 0, 0.08)",
};

// The custom properties defined in code.
const CODE = {
  "--color-surface-primary": "#ffffff",
  "--color-surface-secondary": "#f7f7f8",
  "--color-border-default": "#b7bcc2",
  "--color-text-primary": "#1f2328",
  "--color-text-secondary": "#5a6169",
  "--color-action-main": "#275ea5",
  "--color-action-on-main": "#ffffff",
  "--color-action-secondary": "#2f6bb5",
  "--spacing-step-1": "0.25rem",
  "--spacing-step-2": "0.5rem",
  "--spacing-step-3": "0.75rem",
  "--spacing-step-4": "1rem",
  "--typography-body-size": "1rem",
  "--typography-body-line-height": "1.5",
  "--typography-heading-size": "1.5rem",
  "--radius-small": "0.25rem",
  "--radius-medium": "0.5rem",
  "--color-action-danger": "#a12d2d",
};

// Name normalization: the same transliteration rule established in the naming lesson.
const TRANSLIT = { "’": "'", "‘": "'", "“": '"', "”": '"', "–": "-", "—": "-", "…": "..." };
const normalize = (s) => [...s].map((ch) => TRANSLIT[ch] ?? ch).join("");
const nameNormal = (designName) => "--" + normalize(designName).toLowerCase().replace(/[/\s]+/g, "-");

// Value normalization: color -> six-digit lowercase hex, length -> pixels.
const ROOT_SIZE = 16;
function valueNormal(value) {
  const v = value.trim().replace(/\s+/g, " ");
  const shortHex = v.match(/^#([0-9a-fA-F]{3})$/);
  if (shortHex) return "#" + [...shortHex[1]].map((h) => h + h).join("").toLowerCase();
  if (/^#[0-9a-fA-F]{6}$/.test(v)) return v.toLowerCase();
  const rgb = v.match(/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/);
  if (rgb) return "#" + rgb.slice(1, 4).map((n) => Number(n).toString(16).padStart(2, "0")).join("");
  const rem = v.match(/^(-?[\d.]+)rem$/);
  if (rem) return `${Number(rem[1]) * ROOT_SIZE}px`;
  const px = v.match(/^(-?[\d.]+)px$/);
  if (px) return `${Number(px[1])}px`;
  return v;
}

// Three-stage comparison: raw, name normalization only, full normalization.
function compare(nameTransform, valueTransform) {
  const left = new Map(Object.entries(DESIGN).map(([a, d]) => [nameTransform(a), valueTransform(d)]));
  const right = new Map(Object.entries(CODE).map(([a, d]) => [a, valueTransform(d)]));
  const designOnly = [...left.keys()].filter((a) => !right.has(a));
  const codeOnly = [...right.keys()].filter((a) => !left.has(a));
  const valueDiff = [...left.entries()].filter(([a, d]) => right.has(a) && right.get(a) !== d).map(([a]) => a);
  return { designOnly, codeOnly, valueDiff, common: left.size - designOnly.length };
}

const stages = [
  ["raw comparison", (a) => a, (d) => d],
  ["name normalization", nameNormal, (d) => d],
  ["name + value normalization", nameNormal, valueNormal],
];

console.log("stage                       common name   design only   code only   value diff   total diff");
let last = null;
for (const [name, nT, vT] of stages) {
  const s = compare(nT, vT);
  last = s;
  const total = s.designOnly.length + s.codeOnly.length + s.valueDiff.length;
  console.log(
    `${name.padEnd(27)} ${String(s.common).padStart(11)} ${String(s.designOnly.length).padStart(13)} ` +
    `${String(s.codeOnly.length).padStart(11)} ${String(s.valueDiff.length).padStart(12)} ${String(total).padStart(11)}`
  );
}

console.log("\n— REAL DEVIATIONS —");
for (const a of last.valueDiff) {
  const designKey = Object.keys(DESIGN).find((k) => nameNormal(k) === a);
  console.log(`value diff: ${a}\n    design: ${DESIGN[designKey]} → ${valueNormal(DESIGN[designKey])}\n    code  : ${CODE[a]} → ${valueNormal(CODE[a])}`);
}
for (const a of last.designOnly) console.log(`design only: ${a} (${DESIGN[Object.keys(DESIGN).find((k) => nameNormal(k) === a)]})`);
for (const a of last.codeOnly) console.log(`code only: ${a} (${CODE[a]})`);

// Sync rate and gate: at what threshold does a release get blocked?
const totalTokens = new Set([...Object.keys(DESIGN).map(nameNormal), ...Object.keys(CODE)]).size;
const realDiff = last.designOnly.length + last.codeOnly.length + last.valueDiff.length;
console.log(`\ntotal tokens (union): ${totalTokens}`);
console.log(`real deviation: ${realDiff}   sync rate: ${(((totalTokens - realDiff) / totalTokens) * 100).toFixed(1)}%`);
```

```
stage                       common name   design only   code only   value diff   total diff
raw comparison                        0            18          18            0          36
name normalization                   17             1           1           16          18
name + value normalization           17             1           1            1           3

— REAL DEVIATIONS —
value diff: --color-action-secondary
    design: #3B7BC8 → #3b7bc8
    code  : #2f6bb5 → #2f6bb5
design only: --shadow-elevation-1 (0 1px 2px rgba(0, 0, 0, 0.08))
code only: --color-action-danger (#a12d2d)

total tokens (union): 19
real deviation: 3   sync rate: 84.2%
```

## Eliminating False Diffs

The three-stage table explains why the diff report goes unread in most organizations.
The raw comparison finds 36 diffs, and **none of them are real**; the two sides are
using different naming conventions. A report like this gets read once, then
gets closed.

Name normalization brings the diff down to 18, using the same transliteration rule
established in the naming lesson — its cost was paid there, and its return is
collected here. Without that convention, this step would need its own mapping table,
one that would go stale as tokens were added.

Value normalization brings the diff down to 3. The 15 eliminated diffs came from three
format distinctions: hex-notation letter case, three-digit short notation, the `rgb`
functional notation, and length units written as root-relative instead of pixels. None
produce a visual deviation; both sides describe the same color and the same length.

The order itself is a design decision: normalization happens **before** the diff is
computed, not after. Computing the diff first and then trying to eliminate it means
deciding which diff is false all over again every time.

## Reading the Remaining Three Deviations

Each of the three remaining rows produces a different piece of work.

**Value diff** — `--color-action-secondary` differs between the two sides. This is a
violation of the source of truth: one side was changed without waiting for the other.
Even once the source's direction has been decided, this row alone does not say which
side is correct; what it says is that the flow broke somewhere. Given that this color
is the secondary action color, and that the typography and color lessons computed the
contrast ratio between it and the primary action color, the deviation's consequence may
not be visual but functional.

**Design only** — `--shadow-elevation-1` exists in the design library but not in code.
This is a design decision that was never implemented, and it enters the contribution
process as a piece of work. This row staying in the report keeps the decision from
being forgotten.

**Code only** — `--color-action-danger` exists in code but not in design. A team
needed a color for a destructive action and added the token on the code side. This
resolves into one of two outcomes: the token is legitimate and gets added to the
design library, or it is not and gets removed. Left undecided, it stays, gets used, and
its counterpart cannot be found the next time a theme is derived.

The sync rate — 84.2% — is these three rows reduced to a single number, and the same
gate built for name matching in the naming lesson can be built here too: when the rate
falls below a floor, the release is blocked. What matters is not the number itself but
that it is **measured on every release**; a deviation is not a one-time event, it is a
quantity that accumulates.

## The Layer the Chain Cannot Cover

The line between what the toolchain can and cannot do is sharp, and it explains why
the structure built across this lesson cannot be built by tooling alone.

The chain syncs **values**: color, spacing, typography, radius, shadow — name-value
pairs that can be compared in a machine and generated automatically.

The chain cannot sync **behavior**. Which state a button takes on which color, where a
modal sends focus, how a dropdown is navigated by keyboard cannot be exported from a
design tool. This layer is synced by the naming convention built in the second lesson,
the doc sections defined in the third lesson, and the review gates built in the fifth
lesson. The toolchain is the guardian of the token layer; the guardian of the component
layer is governance.

## Summary

- If a value is kept in two places, the direction of the source of truth must be
  decided in advance; two-way sync is not an option because it produces unresolvable
  conflicts. The chain's shape is independent of direction: export, transform, and
  multiple target formats.
- A diff report is unreadable without normalization; in the example, 33 of 36 diffs
  were false diffs coming only from naming-convention differences.
- Normalization happens before the comparison; the name rule is inherited from the
  naming convention, the value rule reduces colors to a single notation and lengths to
  a single unit.
- The remaining deviations fall into three classes: a value diff shows the flow broke
  somewhere, a design-only token shows an unimplemented decision, a code-only token
  shows an unrecorded addition.
- The sync rate is measured on every release and tied to a floor; a deviation is not an
  event but a quantity that accumulates.
- The toolchain syncs values; it cannot sync behavior. The component layer's equality
  is guaranteed by the naming convention, doc sections, and review gates.

## Course Wrap-Up

This topic built the layer of a design system that comes after values. What enters the
catalog was tied to a scope criterion, and how ready an entry is was tied to maturity
levels' exit conditions. The shared language between design and code was turned into a
derivation rule, and the mismatched names were counted. Documentation was split into seven
required sections, and its coverage was computed weighted by usage. The version number
stopped being a debatable topic and became the result of the interface diff; migration
cost was measured by call site. The contribution flow became a state machine,
governance models were compared by queue and deviation counts, adoption was measured
through a repository scan, and finally, the token layer's equality was tied to a diff
report.

What all this structure has in common is that every decision was tied to a number:
coverage percentage, gap impact, migration cost, queue time, deviation count, sync
rate. A design system's success is tracked by these numbers; untracked, it turns into
a library no one notices has been abandoned.

The catalog is built and governed. But every entry in the catalog still leaves one
question unanswered: how is this component used with a keyboard, where does it send
focus, how does it announce itself to a screen reader? The third lesson defined an
"accessibility note" as a doc section and counted it among the stable maturity level's
exit criteria, but it never said what goes inside that note. The next course,
**Accessible Component Patterns**, fills this gap: it spells out, one by one, the
keyboard behavior, focus management, and screen-reader information for every component
in the catalog, from button to dropdown, from tabs to modal; it designs focus traps for
complex interaction patterns and makes interface text and localization requirements
part of the system.
