Skip to main content

Naming Things: Design Tokens That Survive Growth

By · · 6 min read

On this page

There's an old joke in software that the two hardest problems are cache invalidation and naming things. Design systems don't have much cache invalidation, but they make up for it with naming.

Every design system I've worked on has eventually hit the same wall. The early tokens were named quickly — blue-500, gray-light, spacing-medium — and they worked fine when the product was small. Then the product grew. Dark mode arrived. A second brand appeared. A new platform needed support. Suddenly the names didn't mean anything anymore, and changing them meant touching hundreds of files.

Good token naming is one of those things that feels like overthinking at the start and feels like foresight a year later. Here's the approach I've settled on.

What tokens are for#

Design tokens are named values for design decisions: colors, spacing, typography, radii, shadows, motion. Instead of hardcoding #1A73E8 across a codebase, you reference a token, and the value lives in one place.

The obvious benefit is consistency. The deeper benefit is that tokens let you change decisions without hunting for every place they were applied. Rebrand the primary color? Change one value. Add dark mode? Swap a set of values. Support a second brand? Map a different palette onto the same names.

But that only works if the names describe the right thing. And that's where most systems go wrong.

The problem with naming by value#

The most common early mistake is naming tokens after what they look like. blue, light-gray, big-shadow, 16px-spacing.

These names break the moment the value changes. If the brand moves from blue to green, a token called blue that now contains green is actively misleading. If dark mode makes your "light gray" surface dark, the name becomes nonsense. Developers start working around the names instead of trusting them.

Three layers#

The structure that has held up best for me uses three layers of tokens, each with a different job.

1. Primitive tokens#

Primitives are the raw palette: every color, size, and value available to the system. These are named by value, because their job is to describe the raw material.

Examples: color-blue-600, color-neutral-100, space-4, radius-8.

Primitives should rarely be used directly in product designs. They're the paint, not the painting.

2. Semantic tokens#

Semantic tokens describe purpose. They reference primitives, and they're what designers and developers use day to day.

Examples: color-text-primary, color-surface-raised, color-border-subtle, color-action-primary, color-status-error.

This is the layer that makes theming possible. In light mode, color-text-primary might point to a near-black primitive; in dark mode, to a near-white one. The product code doesn't change at all — only the mapping does.

3. Component tokens#

Component tokens are optional and apply to specific components when they need their own decisions. They usually reference semantic tokens.

Examples: button-primary-background, input-border-focus, card-padding.

I use these sparingly. They're valuable for complex components that need fine-grained theming, but creating them for everything leads to an explosion of tokens that nobody can keep track of.

Naming conventions that age well#

Within those layers, a few conventions have helped names stay meaningful as systems grow.

Use a consistent order. I typically follow category, then property, then variant, then state: color-text-secondary, color-action-primary-hover. Consistency matters more than which order you pick. When every token follows the same grammar, people can guess names correctly without looking them up.

Name by role, not by position. color-surface-raised describes what the surface does. color-card-background ties the token to one component and invites misuse elsewhere.

Prefer purpose over intensity. color-text-secondary ages better than color-text-light, because "light" stops making sense in dark mode.

Be careful with numeric scales. Scales like space-1 through space-10 are fine for primitives. For semantic spacing, sometimes named sizes — space-inline-tight, space-stack-section — communicate intent better. There's no single right answer, but pick deliberately.

Leave room to grow. If your primitive scale goes 100, 200, 300, you can insert 150 later without renaming everything. If it goes 1, 2, 3, you can't.

Status and feedback colors#

Status colors deserve special attention because they carry meaning that users rely on. I define a consistent set — usually success, warning, error, and info — and give each the same set of roles:

  • A background for subtle containers.
  • A border.
  • A text color that meets contrast requirements.
  • An icon or emphasis color.

So color-status-error-background, color-status-error-border, color-status-error-text, and so on. This predictability means that when someone builds a new kind of alert or badge, all the pieces they need already exist and already work together.

Making tokens shared#

Tokens are only valuable if design and code actually use the same ones. A token system that lives only in Figma, or only in code, slowly drifts.

A few practices help keep them in sync:

  • One source of truth. Tokens are defined in a single format that can generate both Figma variables and code.
  • Same names in both places. If Figma calls it text/primary and code calls it textPrimary, that's fine as long as the mapping is obvious and automatic.
  • Changes go through review. Adding or renaming a token is a system change, discussed and documented, not a quick tweak.
  • Document intent. Each semantic token gets a short description of when to use it — and sometimes when not to.

Handling exceptions#

Every system eventually meets a design that doesn't fit. A marketing banner that needs a special gradient. A data visualization that needs twelve distinct colors. A partner integration with its own branding.

The temptation is to add a token for each exception. Sometimes that's right. Often, it's better to let genuine one-offs stay one-off, clearly marked as such, rather than polluting the shared system. A token should exist because a decision is reused, not because a value appeared once.

When the same exception shows up three times, that's the signal to promote it into the system.

Migration without pain#

If you're inheriting a system with messy names, a complete rename can be daunting. A gradual approach works better:

  1. Introduce the new semantic layer alongside the old tokens.
  2. Map old tokens to new ones, so both work during the transition.
  3. Migrate high-traffic components and screens first.
  4. Mark old tokens as deprecated, with clear guidance on replacements.
  5. Remove old tokens once usage reaches zero.

It takes longer than a big-bang rename, but it doesn't stop product work, and it doesn't break things in the meantime.

Names are agreements#

Ultimately, token names are agreements between people. They encode a shared understanding of what each decision is for. When the names are good, conversations get faster: a designer and a developer can say "use surface raised here" and both know exactly what that means, in every theme, on every platform.

That's what makes naming worth the effort. Not elegance for its own sake, but a shared language that keeps working as the product, the team, and the brand grow far beyond what anyone imagined on day one.