Module 1: What A Design System Is

Consistency and the cost of drift

Overview

Consistency is one of those words everyone approves of and almost nobody defines. "The UI should be consistent" sounds like a truism, like good taste, like something aesthetic and debatable. This lesson does the opposite: it turns consistency into something measurable, and its enemy —drift— into a number you can calculate. Once consistency stops being an opinion ("it seems uneven to me") and becomes a fact ("there are 3 distinct values for a role that should have 1"), you have a hard argument for setting up a system, and a way to know whether your system is working.

The lesson's thesis is that drift has a cost, and that cost is countable in three currencies: it's paid in trust (an uneven UI feels careless, unprofessional), in time (every divergence is a decision someone had to make and someone will have to reconcile), and in bugs (two values that should be one are two things that can break separately). We're going to audit Mercado's storefront in its ad-hoc version, measure its drift across several roles at once, and put a figure on it: the drift score.

Connection with the module. Lesson 1 measured the drift of a single role (color); lesson 2, of another (spacing). This lesson generalizes: it audits several roles at once and adds up the chaos into a single metric, showing that drift isn't an isolated accident but a property of the whole system. And it connects directly to lesson 3: drift is exactly what happens when the token layer doesn't exist and every component writes its values by hand. Lesson 5 will take the other side of the same coin —how the system not only prevents drift, but makes change cheap (a single source of truth).

An analogy: the orchestra tuning up

Picture an orchestra of thirty musicians about to play. Each instrument, on its own, sounds fine: the violinist tuned their violin, the oboist their oboe, the cellist their cello. Each one is "correct" in isolation.

But if each one tuned on their own —one with an old tuning fork, another by ear, another with an app— their "A" isn't exactly the same "A". One tuned to 440 Hz, another to 442, another to 438. Individually, imperceptible. Together, when all thirty play the same note, the ear feels something's off even without being able to name it: the note is "dirty", it has a beat, it sounds cheap. Nobody played a wrong note; the problem is that the correct notes don't match each other. That's drift: not errors, but truths that don't agree.

That's why orchestras all tune to the same A before starting: the oboe gives an "A", and all thirty tune to that one. Not to "a good A" each, but to the same A, a single reference. That single "A" is the token. The orchestra's consistency doesn't come from every musician being disciplined; it comes from everyone pointing at the same reference. And the cost of not doing it isn't that one instrument sounds bad —they sound fine separately—; it's that the ensemble sounds cheap. Just like a UI: every screen looks fine, but the whole journey feels careless, and the user perceives it even without knowing why.

A closer look: the three currencies of drift

Drift isn't an abstract aesthetic problem. It costs, concretely, in three things.

It costs trust. A user doesn't analyze your CSS, but they feel the inconsistency. Buttons that are almost the same, spacings that almost match, blues that almost go together: the brain registers the "almost" as carelessness, and carelessness as unprofessionalism. In a storefront like Mercado, where the user is about to hand over their card, that feeling of "something isn't polished" is money walking away. Consistency is, in part, a promise: "this is well cared for, you can trust it."

It costs time. Every diverging value represents a decision someone made (badly, from memory) and a decision someone will have to undo later. When the team decides "let's unify the buttons", the accumulated drift turns into hours of hunting: finding every blue, every padding, every scattered radius, and reconciling them. The time ad-hoc "saved" when it was written gets paid back with interest when it's cleaned up.

It costs bugs. Two values that should be one are two things that can break separately. If the buttons' focus color is written by hand in five places, and a contrast problem gets fixed in four but forgotten in the fifth, there's an accessibility bug nobody sees until a low-vision user can't use that button. A single source of truth can't have this bug: it gets fixed in one place and holds for all of them. Drift multiplies the surface where things can go wrong.

The three currencies share one root: drift is having more than one truth for a single idea. Measuring it is, then, counting how many truths exist where there should be one.

Worked example: auditing the drift of a whole storefront

So far we've measured one role at a time. A real storefront has many roles diverging in parallel: the brand color, the spacing between cards, the corner radius, and a dozen more. Let's audit three at once and summarize the chaos into a single metric.

The idea behind the drift score: for each role, the ideal is 1 value. Anything beyond 1 is drift. If a role has 3 distinct values, it contributes 3 - 1 = 2 to the drift (two extra decisions nobody should have had to make). We add up that excess across every role and get one number: how many leftover decisions the storefront is carrying. We normalize the units (as in lesson 2) so we don't confuse 1rem with 16px:

// L4 - the cost of drift: we audit 3 roles in the ad-hoc storefront and count
// how many "distinct decisions" exist for what should be just ONE.
const adHoc = {
  'color.primary': ['#3b82f6', '#2563eb', '#3b82f6', '#3c83f6'],
  'space.card-gap': ['16px', '1rem', '18px', '16px'],
  'radius.card': ['8px', '6px', '8px', '0.5rem'],
};

function normalize(role, value) {
  if (role.startsWith('space') || role.startsWith('radius')) {
    return value.endsWith('rem') ? Math.round(parseFloat(value) * 16) + 'px' : value;
  }
  return value.toLowerCase();
}

console.log('=== Ad-hoc storefront drift audit ===\n');
let driftScore = 0;
for (const role of Object.keys(adHoc)) {
  const distinct = [...new Set(adHoc[role].map((v) => normalize(role, v)))];
  driftScore += distinct.length - 1; // 1 = ideal; whatever is left over is drift
  console.log(role.padEnd(16) + distinct.length + ' distinct values  ' + JSON.stringify(distinct));
}
console.log('\nDrift score (leftover decisions): ' + driftScore);
console.log('With a system, each role = 1 token  ->  drift score: 0');

What to expect. Running the file, the output is exactly this:

=== Ad-hoc storefront drift audit ===

color.primary   3 distinct values  ["#3b82f6","#2563eb","#3c83f6"]
space.card-gap  2 distinct values  ["16px","18px"]
radius.card     2 distinct values  ["8px","6px"]

Drift score (leftover decisions): 4
With a system, each role = 1 token  ->  drift score: 0

Read it like a medical chart for the storefront. Each line is a role and its level of "infection":

color.primary → 3 distinct values. Of the four written blues (#3b82f6, #2563eb, #3b82f6, #3c83f6), three are truly distinct —#3b82f6 repeats, but #2563eb and #3c83f6 are different from it and each other. Three truths for "the brand blue". It contributes 3 - 1 = 2 to the drift score.

space.card-gap → 2 distinct values. The 1rem normalized to 16px (a false alarm, merges with the 16pxs), but the 18px really is a different value. Two remain: 16px and 18px. It contributes 2 - 1 = 1.

radius.card → 2 distinct values. The 0.5rem normalized to 8px (merges with the 8pxs), but the 6px is different. 8px and 6px remain. It contributes 2 - 1 = 1.

Total: 2 + 1 + 1 = 4. The ad-hoc storefront carries 4 leftover decisions —four divergences nobody chose and someone will have to reconcile. That number is the cost of drift turned into a metric. It isn't an opinion ("it looks uneven"); it's a countable fact, and you can watch it over time: if it goes up, your UI is getting messier; if it's 0, every role has a single truth.

And the last line is the system's promise: with tokens, each role is a single token, so each role has exactly 1 value, and the drift score is 0 by construction. Not "0 if the team is disciplined", but 0 structurally: there's nowhere to diverge. That's the hard argument. Consistency isn't an ideal you strive for; it's the default state when values come from tokens.

A question to carry into the next lesson: the drift score measures the current disorder. But there's a second, even more expensive cost: when you want to change one of these roles on purpose (say, darken the brand blue), ad-hoc forces you to edit every place by hand. How many edits? Lesson 5 measures that.

A nuance: consistency is not uniformity

Watch out for a naive reading of all this. "Consistency" doesn't mean everything is the same —that would be a flat, boring UI. It means what fills the same role should be the same, and what fills different roles should differ systematically.

A primary button and a secondary button should look different: one is the main action, the other the alternative. That's not drift; it's a variant, an intentional difference the system encodes (lesson 5). Drift is the opposite: when two things with the same role —two primary buttons, two gaps between cards— differ without intent. The test to tell them apart: ask yourself "did someone decide this difference, and does it mean something?" If yes, it's intentional variation (good). If "it just came out that way", it's drift (bad). A mature system has plenty of variation —variants, scales, themes— but zero drift: every difference is on purpose.

Common mistakes

Treating consistency as a matter of taste. What happens: when someone points out the UI looks uneven, the reply is "looks fine to me" or "it's subjective". Why it happens: without a metric, consistency looks like opinion. How to spot it: arguments about "does this look good?" never end because there's no data to settle them. How to fix it: measure. Count how many distinct values exist for each role. "The brand blue has 3 values" isn't opinion; it's a fact, and its fix (unify into a token) is objective. The drift score turns an aesthetic argument into a technical task with a "done" criterion: drift score 0.

Confusing drift with intentional variation. What happens: in the eagerness to "eliminate inconsistencies", someone makes the primary and secondary buttons look identical, killing a difference that should exist. Why it happens: "consistency" gets read as "everything the same". How to spot it: your UI lost hierarchy —you can no longer tell the primary action from the secondary one— in the name of uniformity. How to fix it: the goal is zero drift (differences without intent), not zero differences. Differences that mean something (primary vs. secondary, headline vs. body) are system variants and are good. Only the differences nobody chose get eliminated. Consistency is "the same role, the same"; not "everything, the same".

Believing drift gets fixed by "being careful". What happens: faced with drift, the proposed solution is "let's be more disciplined, let's always use the same value". Why it happens: drift gets seen as a problem of human carelessness. How to spot it: every so often there's a style "cleanup campaign", and shortly after, the drift is back. How to fix it: human discipline doesn't scale —with enough screens and people, someone will write 15px from memory. The only solution that scales is structural: make it so the value can't be written by hand, because it comes from a referenced token. Don't ask for care; remove the chance to make the mistake. A system doesn't trust memory; it makes memory unnecessary.

Exercises

Exercise 1 — Calculate the drift score. Without running anything, calculate the drift score for this audit of two roles. Remember: per role, distinct values - 1, and normalize rem to px (base 16):

const audit = {
  'color.text':   ['#111827', '#111827', '#1f2937', '#111827'],
  'space.section': ['32px', '2rem', '32px', '40px', '2.5rem'],
};
See solution
  • color.text: values #111827, #111827, #1f2937, #111827 → distinct: #111827 and #1f29372. Contributes 2 - 1 = 1.
  • space.section: normalizing (2rem → 32, 2.5rem → 40): 32, 32, 32, 40, 40 → distinct: 32px and 40px2. Contributes 2 - 1 = 1.

Drift score = 1 + 1 = 2.

Notice what normalization did to space.section: of the five written values (32px, 2rem, 32px, 40px, 2.5rem), which looked to the eye like "four or five different things", there are really only two actual values (32 and 40); the rems were the same measurements written differently. The real drift (2) was smaller than it appeared, but it's still drift: two truths where the system would have one (space-8 = 32px, say) or two intentional tokens if 32 and 40 were different roles.

Exercise 2 — Drift or variant? For each difference observed in the storefront, say whether it's drift (unintentional divergence, to fix) or a variant (intentional difference, to keep):

  • (a) The "Add to cart" button is blue; the "Cancel" button is gray.
  • (b) The gap between cards is 16px on the home page and 15px in the catalog.
  • (c) The page title uses text-xl; the product name uses text-lg.
  • (d) The ProductCard's corner radius is 8px on three screens and 6px on one.
See solution
  • (a) Variant (keep). Blue vs. gray for primary vs. secondary is an intentional difference: it communicates hierarchy (main action vs. alternative). It's the variant system working, not drift.
  • (b) Drift (fix). 16px vs. 15px for the same role (the gap between cards) is a divergence nobody chose —the 15px came from writing it from memory. Unify into a token (space-4).
  • (c) Variant (keep). Different sizes for the page title vs. the product name reflect intentional hierarchy (one is more important than the other). Both come from the type scale; they're different roles, not the same role diverging.
  • (d) Drift (fix). The same component (ProductCard) with different radii across screens is unintentional divergence. The 6px is the intruder. Unify into radius-md.

The test, again: does the difference mean something, and did someone decide it? (a) and (c) yes → variants. (b) and (d) no, "it just came out that way" → drift.

Exercise 3 — The three currencies. For this scenario, identify which of the three currencies —trust, time, bugs— the drift is being paid in, and explain how a system would have prevented it:

"In Mercado's storefront, the buttons' focus color (the border that appears when navigating with the keyboard) is written by hand in 6 files. Last week it was discovered that in 2 of them the focus was barely visible —insufficient contrast. It was fixed in those 2. Today a user reported that on the checkout page the focus still isn't visible."

See solution

All three currencies are being paid, and the scenario illustrates them in a chain:

  • Bugs. The direct symptom is an accessibility bug: the focus isn't visible on checkout. It exists because the value was written by hand in 6 places —6 truths for a single role ("the focus color"). With a single source of truth, this bug is impossible: there's one --color-focus, it gets fixed once, and it holds for all 6.
  • Time. The fix "in those 2" was manual work, and on top of that incomplete —4 were left unreviewed, one of which just broke. Every round of fixing is a hunt through every file, and there's always the risk one slips through. That time (fixing, plus re-fixing what slipped through) is drift's time cost.
  • Trust. A focus that's invisible on checkout —the screen where the user hands over their card— is exactly where the feeling of "this isn't polished" costs the most. The user who reported the problem already lost some trust that the site is well cared for.

How a system prevents it: the focus color is a token (color.focus), applied by the Button component in a single place. There aren't 6 places; there's 1. The contrast bug gets fixed in the token, and all 6 uses —including checkout— end up fixed at once, with no hunting and no oversights. The drift isn't that "they forgot a file"; it's that there were 6 files where there should have been 1.

Summary and next step

In this lesson you turned consistency from an opinion into a measurement. With the orchestra tuning up, you saw that drift isn't errors —each instrument sounds fine alone— but truths that don't agree, and that the ensemble "sounds cheap" even though nobody plays a wrong note. You learned the three currencies of drift's cost —trust, time, and bugs— and their common root: having more than one truth for a single idea. You measured it by running code: auditing three roles of the ad-hoc storefront gave a drift score of 4 —four leftover decisions nobody chose—, and with tokens that score is 0 by construction, not by discipline. And you refined the concept: consistency is not uniformity; the goal is zero drift (differences without intent), not zero differences (intentional variants are good).

Before moving on you should be able to: calculate a drift score given a set of roles and their values; name the three currencies of drift's cost; tell drift apart from intentional variation; and explain why "being careful" doesn't scale but structure does.

Lesson 5 looks at the other side of the same coin. The drift score measures the disorder that already exists; but the system's strongest argument isn't only that it prevents drift, it's that it makes change cheap: when you want to change a value on purpose, ad-hoc charges you N edits (and the risk of missing one, as in the focus exercise), while the system charges you 1. That's maintainability and the single source of truth, and you'll measure it: one change, N automatic updates.

Resources