Five Token Anti-Patterns
Token systems rarely fail by lacking a feature; they fail by acquiring habits. The design tokens best practices that matter most are inversions of five recurring failures — token sprawl, layer skipping, the escape-hatch habit, naming by appearance, and the write-only file. Each is an anti-pattern in the software-engineering sense: a common response to a recurring problem that trades a small cost now for a compounding cost later.
Each section below names the failure as an observable symptom, the mechanism behind it, and the fix. The list assumes the fundamentals; if tokens are new territory, start with our design tokens guide and come back with a file in hand.
1. Token sprawl — a token per use-site
The symptom: more tokens than decisions. The file holds
button-3-left-padding, card-header-title-color, modal-footer-gap —
hundreds of names — and finding the right one takes longer than minting
another, so everyone mints another.
The mechanism: naming the place instead of the decision. A token records one decision that many places share; a token per use-site records locations, so the count grows with the UI’s surface area rather than with the number of decisions — and the sharing that made tokens useful never happens. Change “the gap after a section heading” and you hunt through forty location-names to work out which ones meant that.
The fix: roles, and the courage to reuse them. gap-section used in
forty places is one decision honestly recorded; forty tokens is a phone book.
2. Layer skipping — components reading primitives
The symptom: a rebrand touches components. The palette re-derives cleanly, and then someone spends a week editing component files anyway.
The mechanism: background: var(--brand-600) inside a component
hard-codes an assumption — that this primitive step happens to do this job
today. The semantic layer exists to
hold that assumption in exactly one
place; a component that skips it restates the assumption locally, and every
local restatement is a place where change no longer stops.
The fix: the semantics-only consumption rule. Components read role names —
accent, surface, text-primary — and leave ramp steps to the semantic
layer that points at them.
3. The escape-hatch habit — “just this once, #2564ec”
The symptom: the token file describes a system the product doesn’t quite
use. Grep the production styles and raw values sit beside the tokens —
near-misses like #2564ec, one digit off #2563eb, indistinguishable on any
screen and invisible in review.
The mechanism: each one-off is locally rational — a deadline, a special case, a value that “didn’t seem worth a token” — and each lowers the cost of justifying the next. The file stays tidy while its authority leaks, until it documents intent rather than sourcing values. Type scales teach the same lesson with off-scale sizes — the 17px “just this once” — and it generalizes to every token type.
The fix: value changes land in the file first. If a value deserves to ship, it deserves a name; if no existing role fits, that mismatch is itself a decision worth recording. The rule is cheap to enforce because violations are mechanically findable — see the table below.
4. Naming by appearance — gray-light, blue-dark
The symptom: dark mode makes the names lie. gray-light now stores a
dark gray, text-dark renders light, and every appearance-based name is
wrong in exactly the theme where confusion costs most.
The mechanism: an appearance name welds the name to the current value instead of to the job. Names are the durable half of a token — outliving values is their purpose — so a name that describes today’s value carries an expiry date nobody can see.
The fix: split the naming by layer.
Semantic names describe roles —
text-primary, surface, accent — and primitives take positional names —
gray-100 through gray-900 — that stay true in any theme because they
describe position on a ramp, not rendered appearance. A dark theme then
re-points the same role names at different steps, and no name changes
meaning.
The five fixes converge on one recognizable shape. Open a healthy token system in Scale Composer — a compact semantic layer of role names (roughly seventeen derived roles, each checked against contrast floors) over positionally named ramps, with a dark section that re-points the same roles, and every value accounted for by a name. It is what the absence of all five anti-patterns looks like in a file.

5. The write-only file — exported once, never re-imported
The symptom: the tool that generated the tokens can no longer touch them. Hand edits have accumulated in the file since the export, the tool’s own state describes a system that no longer exists, and regenerating would overwrite real work — so nobody dares, and the “generated” file is now maintained like any other hand-kept list.
The mechanism: one-way flow. When tooling only exports, every post-export edit widens the gap between what the tool believes and what the file says; the gap makes regeneration destructive; destructiveness makes the tool unusable; and the team is back to hand-maintaining values, now with extra steps.
The fix: make the round-trip loop the only write path. The file re-enters the tool on import, changes happen there or are preserved through it, and export writes back with untouched sections intact. Tools differ in whether they support this at all — worth testing before adoption rather than after the gap has opened.
How do you check an existing system for all five?
Each anti-pattern is detectable with a grep or one honest question — a health check that fits in a coffee break:
| Anti-pattern | Check | Healthy answer |
|---|---|---|
| Token sprawl | grep -c '\$value' tokens.json, then count the decisions you can actually name | the two counts are the same order of magnitude |
| Layer skipping | grep -rn brand-600 src/components/ (any primitive name works) | no hits — components consume roles |
| Escape hatches | grep -rn '#[0-9a-f]\{6\}' src/styles/ --exclude=tokens.css | no raw hex outside the generated output |
| Appearance names | grep -in -e light -e dark -e bright tokens.json | hits appear as theme labels, not inside role names |
| Write-only file | when did the tool last read this file? | recently — the loop is alive |
The exact regexes matter less than the property they demonstrate: every one of these failures is observable. None requires taste to detect, which means none requires seniority to fix.
What do the five have in common?
Every one is a shortcut that trades a small now-cost for a compounding later-cost: naming the decision, adding the layer hop, tokenizing the one-off, choosing the role name, closing the loop — each costs minutes at the moment of writing, and skipping it charges someone else a multiple later. The parallel to technical debt is worth declaring literally rather than leaving as loose metaphor: token debt accrues interest (each shortcut makes the next easier to justify), it compounds silently (the file still parses; nothing looks broken), and it comes due at the worst moments — rebrands, dark mode, tool migrations — which are exactly the events tokens were adopted to make cheap.
Audit your own file
Run the table’s five checks against your token file, then compare shapes. Hold your system against a healthy reference — import your file into Scale Composer or generate a reference set from your brand color, and see where your semantic layer, your naming and your round-trip stand against the five.