Delta E for Design-System Color Changes
Audit design-system color changes with deltaEOK and CIEDE2000 while keeping contrast, gamut, and semantic review independent.
Delta E gives design-system teams a reproducible magnitude for color-token changes without pretending one number can approve the migration. This tutorial compares deltaEOK and CIEDE2000, then keeps contrast, gamut, and semantic review as independent gates.
Delta E adds magnitude to color review
Delta E measures distance between colors in a chosen color space or difference formula. In a design-system migration, it answers “how large is this token change under this metric?” It does not answer whether contrast passes, whether the color is in the output gamut, whether brand meaning is preserved, or whether the change looks acceptable in context. Those are separate gates.
Start with paired token values and provenance. Parse old and proposed colors, normalize them into the chosen reference space, compute one named difference metric, and preserve both coordinates. A naked number without formula, conversion path, precision, and input colors cannot be reproduced. The first figure shows an OKLab field with a baseline point, candidate vectors, and equal-distance rings.
The downloadable artifact uses original illustrative design tokens. It converts sRGB hex values through linear sRGB and XYZ to Lab, computes CIE76 and CIEDE2000, and also converts to OKLab for Euclidean deltaEOK. The fixture proves its math against included reference pairs and deterministic token inputs. It does not establish universal perceptual thresholds or certify accessibility.
Keep the visual specimen beside the numeric receipt. A semantic color appears in text, icons, borders, focus rings, charts, and layered surfaces, so the same coordinate shift can carry different product consequences. Review the resolved token in those roles rather than approving a table in isolation. The metric narrows attention; the specimen reveals whether the intended hierarchy and feeling survived. Include both themes, disabled and focus states, small text, large surfaces, and any data series that relies on the token. Archive the specimen with the exact resolved token set and policy version for later regression review.
Semantic equivalent: the illustrative candidates are listed below.
- Baseline
- Center circle.
- Candidate A
- Square, 0.105 away, mostly along lightness.
- Candidate B
- Circle, 0.130 away along the a axis.
- Candidate C
- Triangle, 0.135 away diagonally in lightness and b.
- Shape, number, and axis text repeat every relationship encoded by color or position.
Name the Delta E formula every time
CSS Color 4 defines color difference concepts and deltaEOK. A color difference metric must name its space and formula. Euclidean distance in OKLab is straightforward and useful for modern color workflows. CIEDE2000 applies corrections in Lab to better model several perceptual effects. CIE76 is plain Euclidean Lab distance. These values are not interchangeable, so a field named only deltaE creates avoidable ambiguity.
Use explicit keys such as deltaEOK, deltaE2000, and deltaE76. Store enough precision for recomputation and round only for display. If a tool changes conversion libraries or reference white handling, version the computation and regenerate the receipt. A design token migration should never compare a value computed in one metric with a threshold chosen for another.
The article’s primary operational metric is Delta E as a family, while each numeric table labels its formula. The artifact treats CIEDE2000 reference pairs as correctness checks and reports deltaEOK for token triage. This is a bounded engineering choice, not a claim that one formula perfectly predicts every observer, display, surround, or semantic use.
Build a reproducible token comparison pipeline
Resolve aliases before comparison, but preserve the authored reference path. A token that still points to the same alias may change because its dependency moved; a token with a new literal may remain visually identical after normalization. Compare resolved colors and report dependency provenance. The Design Tokens Community Group’s color module provides a typed model for color spaces, components, and alpha that is more precise than treating every color as an opaque string.
Normalize missing alpha and reject malformed or unsupported spaces. For wide-gamut values, avoid silently clipping to sRGB before measuring unless that is the declared target. The artifact uses sRGB hex inputs only and says so. A production pipeline should preserve source coordinates through Display-P3 and CSS color-system decisions before computing the metric intended for the target.
Emit one row per token: old source, new source, resolved coordinates, metric version, Delta E values, contrast results for named foreground/background roles, gamut status, alias path, and policy outcome. Stable sorting by token name makes diffs reviewable. A hash over normalized inputs and policy prevents a stale report from being attached to newer tokens.
Read a token Delta E heatmap carefully
A heatmap helps reviewers find large changes, but bins are a product policy rather than a scientific truth. Use labelled ranges, print the numeric value in every cell, and include a non-color cue such as a symbol or border. Sort by component or semantic role so related changes appear together. The second figure compares illustrative surface, text, action, border, success, and warning tokens.
Do not let one loud change hide a dangerous quiet one. A small text-color shift can cross a contrast boundary, while a large decorative accent change can remain accessible. Review both the metric and the component context. Delta E is a triage signal that directs eyes; it is not an approval score.
The fixture heatmap values are labelled illustrative and are not copied from the executable receipt. The public artifact computes its own values from embedded hex pairs. Reviewers can change one proposed token and see its row, category counts, and digest change. This local recomputation is stronger evidence than a static screenshot, while the figure remains useful for explaining how a migration dashboard should encode magnitude.
| Token | Illustrative deltaEOK | Review cue |
|---|---|---|
| surface | 0.012 | Low circle |
| text | 0.025 | Low circle |
| action | 0.072 | Focused triangle |
| border | 0.061 | Focused triangle |
| success | 0.108 | Component square |
| warning | 0.142 | Component square |
Values are explanatory fixtures, not the executable receipt.
Separate difference, contrast, and gamut gates
A safe color migration applies at least three independent checks. Difference asks how much the color moved. Contrast asks whether a foreground/background relationship meets the chosen accessibility policy. Gamut asks whether the authored color can be represented in the target output space and what mapping occurs. Delta E cannot substitute for either of the other two.
The third figure is a three-gate matrix. A candidate can have low difference and fail contrast, high difference and pass contrast, or pass both while requiring gamut mapping. Text labels and shapes show each status. The final decision also needs semantic and brand review: success must still read as success, focus must remain visible, and charts must not collapse distinct series.
Use OKLCH foundations to author systematic lightness, chroma, and hue changes, then evaluate the actual resolved colors. Relative color syntax can preserve relationships, but relative-color palette generation does not remove the need to test rendered pairs. Formulaic inputs can still cross contrast or gamut limits when themes and component contexts differ.
Choose review thresholds from risk and evidence
No single Delta E threshold fits every design-system decision. A barely noticeable shift may be unacceptable for a calibrated data legend; a visible change may be the purpose of a brand refresh. Define bands as workflow triggers: auto-report, focused visual review, or mandatory component review. Record that these are organization-specific review bands, never universal just-noticeable-difference claims.
Color.js documents several color-difference methods and exposes their distinct names. A useful internal tool can show two metrics side by side when teams are migrating methodology, but it should nominate one policy metric to avoid shopping for the friendliest number. Treat disagreements as prompts for inspection rather than errors to average away.
Calibrate bands with representative components, target displays, themes, and human review. Preserve the sample and policy version, and carry it through the design token migration contract. The artifact ships with illustrative bands solely to demonstrate classification; it prints that boundary in the receipt. Teams should replace those bands through a reviewed design-system decision and keep historical values so a later policy change does not rewrite old approvals.
Runnable artifact — Declared color-difference math; not a universal perception, contrast, gamut, or accessibility verdict.
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
const sha=value=>createHash("sha256").update(JSON.stringify(value)).digest("hex");
const rad=n=>n*Math.PI/180,deg=n=>n*180/Math.PI;
function hex(value){if(!/^#[0-9a-f]{6}$/i.test(value))throw new Error("invalid-srgb-hex");return[1,3,5].map(i=>parseInt(value.slice(i,i+2),16)/255)}
const linear=v=>v<=.04045?v/12.92:((v+.055)/1.055)**2.4;
function lab(value){const [r,g,b]=hex(value).map(linear),x=(.4124564*r+.3575761*g+.1804375*b)/.95047,y=(.2126729*r+.7151522*g+.072175*b),z=(.0193339*r+.119192*g+.9503041*b)/1.08883,f=t=>t>216/24389?Math.cbrt(t):(24389/27*t+16)/116;return[116*f(y)-16,500*(f(x)-f(y)),200*(f(y)-f(z))]}
function oklab(value){const [r,g,b]=hex(value).map(linear),l=Math.cbrt(.4122214708*r+.5363325363*g+.0514459929*b),m=Math.cbrt(.2119034982*r+.6806995451*g+.1073969566*b),s=Math.cbrt(.0883024619*r+.2817188376*g+.6299787005*b);return[.2104542553*l+.793617785*m-.0040720468*s,1.9779984951*l-2.428592205*m+.4505937099*s,.0259040371*l+.7827717662*m-.808675766*s]}
const euclid=(a,b)=>Math.sqrt(a.reduce((sum,v,i)=>sum+(v-b[i])**2,0));
function de2000(x,y){const [L1,a1,b1]=x,[L2,a2,b2]=y,C1=Math.hypot(a1,b1),C2=Math.hypot(a2,b2),C=(C1+C2)/2,G=.5*(1-Math.sqrt(C**7/(C**7+25**7))),ap1=(1+G)*a1,ap2=(1+G)*a2,cp1=Math.hypot(ap1,b1),cp2=Math.hypot(ap2,b2),hp=(a,b)=>{let h=deg(Math.atan2(b,a));return h<0?h+360:h},h1=hp(ap1,b1),h2=hp(ap2,b2),dL=L2-L1,dC=cp2-cp1,dh=Math.abs(h2-h1)<=180?h2-h1:h2<=h1?h2-h1+360:h2-h1-360,dH=2*Math.sqrt(cp1*cp2)*Math.sin(rad(dh/2)),Lm=(L1+L2)/2,Cm=(cp1+cp2)/2,hm=Math.abs(h1-h2)<=180?(h1+h2)/2:(h1+h2<360?(h1+h2+360)/2:(h1+h2-360)/2),T=1-.17*Math.cos(rad(hm-30))+.24*Math.cos(rad(2*hm))+.32*Math.cos(rad(3*hm+6))-.2*Math.cos(rad(4*hm-63)),Sl=1+.015*(Lm-50)**2/Math.sqrt(20+(Lm-50)**2),Sc=1+.045*Cm,Sh=1+.015*Cm*T,Rt=-2*Math.sqrt(Cm**7/(Cm**7+25**7))*Math.sin(rad(60*Math.exp(-(((hm-275)/25)**2))));return Math.sqrt((dL/Sl)**2+(dC/Sc)**2+(dH/Sh)**2+Rt*(dC/Sc)*(dH/Sh))}
const pairs=[{token:"surface",old:"#f8f5ed",next:"#f5f3ec"},{token:"text",old:"#17233d",next:"#202944"},{token:"action",old:"#5e83d8",next:process.argv.includes("--alternate")?"#765fc4":"#6f67ce"},{token:"success",old:"#2f6f57",next:"#16805d"}];const rows=pairs.map(pair=>{const oldLab=lab(pair.old),newLab=lab(pair.next),oldOK=oklab(pair.old),newOK=oklab(pair.next);return{...pair,lab:{old:oldLab,new:newLab},oklab:{old:oldOK,new:newOK},deltaE76:euclid(oldLab,newLab),deltaE2000:de2000(oldLab,newLab),deltaEOK:euclid(oldOK,newOK)}});
const reference=de2000([50,2.6772,-79.7751],[50,0,-82.7485]);assert.ok(Math.abs(reference-2.0425)<.0001);const hostile={hex:""};try{lab("red")}catch(error){hostile.hex=error.message}
const core={schema:"design-token-color-difference-receipt-v1",fixture:"original illustrative sRGB token pairs",conversion:"sRGB D65 to XYZ and Lab with D65 reference white as implemented; OKLab matrices",metrics:["CIE76","CIEDE2000","deltaEOK"],referencePair:{expected:2.0425,actual:reference,tolerance:.0001},rows,hostile,claimBoundary:"Color-difference math for declared inputs only; no universal perceptual threshold, contrast result, gamut approval, or accessibility certification."};console.log(JSON.stringify({...core,receiptHash:sha(core)},null,2));console.log("PASS: token conversions, CIEDE2000 reference oracle, deltaEOK rows, hostile input, alternate pair, and digest verified");
Run the design token migration audit
The public Node artifact parses a frozen set of old and proposed hex tokens, converts them to Lab and OKLab, computes CIE76, CIEDE2000, and deltaEOK, and classifies rows with illustrative review bands. It verifies the CIEDE2000 implementation against published reference-pair values embedded with attribution in code comments. Delta E results remain numeric evidence, not prewritten status claims.
The independent test runs identical input twice and requires exact output. It changes one token and requires a different digest and metric row. Boundary cases include identical black, identical white, a very small channel change, and alpha rejection. Hostile cases reject malformed hex, duplicate token names, missing pair members, and nonfinite intermediates. The test independently recomputes Euclidean OKLab distance from raw coordinates.
The receipt records formulas, conversion assumptions, raw and normalized inputs, coordinates, per-token values, policy bands, provenance, limitations, and a stable hash. It does not measure a display, simulate color-vision differences, or determine WCAG contrast. Those remain separate production tests.
| Candidate | Difference | Contrast | Gamut |
|---|---|---|---|
| A | Pass | Fail | Pass |
| B | Review | Pass | Pass |
| C | Pass | Pass | Map |
Semantic review of meaning, focus, charts, and themes is required for all three.
Make every Delta E approval traceable
Attach the color receipt to the design token migration, then link each exception to a component screenshot and reviewer decision. A DTCG token migration can carry identifiers and alias provenance across format changes. Keep the normalized metric output beside, not instead of, the source token diff.
Review large changes first, then scan contrast and gamut failures regardless of magnitude. Inspect light and dark themes, forced colors where relevant, focus indicators, data visualization, disabled states, and blended overlays. When a token is intentionally changed, write the reason so future reviewers do not “correct” it back toward the baseline merely because its distance is high.
Delta E earns its place by turning a vague visual diff into a reproducible question with a named formula. Run the downloadable audit, replace one proposed color, and trace the changed row through difference, contrast, gamut, and semantic review. Keep the number as evidence; keep the decision human and contextual.