When to Reference Primitives in a Design System

a screenshot of my variables in figma

Primitive tokens are one of the foundational layers of a design system.

They define the raw visual values that everything else is built upon: colors, spacing, typography, radii, shadows, and other foundational properties.

But there is an important distinction that is easy to miss:

Primitives are not usually where designers should make design decisions.

They are the raw materials.

Semantic tokens are where those materials become meaningful.

A designer should rarely think, “I need color blue 700 here.” Instead, the question should be:

“What role does this color play?”

Is it a primary action? A subtle background? A destructive state? Secondary text? A border?

That is where semantic tokens come in.

So, if primitives are the foundation, when should you actually reference them?

1. When creating a semantic token

The most common time I reference primitives is when I am creating or extending a semantic token.

For example, instead of defining a button background as:

#2563EB

I might define:

button.background → blue-600

The semantic token describes the purpose.

The primitive describes the value.

This separation becomes particularly important when your design system supports multiple modes.

A semantic token might look conceptually like:

The semantic token remains the same. Its primitive alias changes depending on the mode.

This is one of the biggest advantages of separating primitives from semantics.

The primitive tells you what the value is. The semantic token tells you why it exists.

2. When auditing contrast

This is where primitives become particularly useful.

If accessibility is part of your design system, contrast checking should not be an afterthought. It should be part of the token creation process.

When creating a semantic token such as:

text.primary

I want to know whether its assigned primitive actually works against the intended surface.

For example:

text.primary → gray-950
surface.default → gray-0

The important question is not whether gray-950 looks good.

The question is whether the combination produces an acceptable contrast ratio.This is why I treat contrast auditing as non-negotiable.

Primitive ramps give you a predictable scale that makes these decisions easier to reason about.

Instead of randomly picking colors until something looks right, you can evaluate relationships between steps.

For example:

gray-950 on gray-0
gray-900 on gray-0
gray-800 on gray-0

You can then determine which combinations meet the required WCAG criteria for the intended use.

This is especially important when designing dark mode and high-contrast themes.

A color that works perfectly in light mode may fail completely in dark mode.

So when creating semantic tokens, I do not just ask:

“Does this look right?”

I ask:

“Does this primitive relationship satisfy the accessibility requirements for this mode?”

3. When communicating design intent

Primitive names are also useful when communicating with other designers and developers.

Consider these two specifications:

Button background: #2563EB

versus:

Button background: blue-600

The second is much more useful inside a design system.

The hex value tells you what the color is.

The token name tells you where it belongs in the system.

This distinction becomes even more valuable when the system evolves.

If the blue ramp changes, the primitive can change while the semantic relationship remains intact.

It also gives developers a shared vocabulary. Instead of discussing arbitrary color values, the team can discuss:

blue-500
blue-600
gray-950

and understand exactly where those values sit within the system.

That makes design specifications easier to maintain and easier to translate into code.

4. When extending the palette

Eventually, your design system will need a color that does not exist in the current palette.

This is where you need to resist the temptation to simply add another random color.

If your system uses 12-step ramps, a new hue should follow the same underlying logic.

For example:

green-0
green-50
green-100
green-200
green-300
green-400
green-500
green-600
green-700
green-800
green-900
green-950

The exact naming is less important than maintaining a consistent progression.

I prefer using OKLCH when constructing these ramps because it gives me a more useful framework for controlling perceived lightness, chroma, and hue.

The goal is not simply to generate twelve increasingly darker hex values.

The goal is to create a ramp with predictable perceptual behavior.

That means the relationship between:

green-100
green-200
green-300
...
green-950

should feel intentional and consistent with the rest of the system.

This becomes particularly important when those primitives eventually power semantic tokens.

Reading a 12-Step Ramp

Once you have a primitive palette, the next question is:

How do you actually choose the right step?

There is no universal rule that says blue-600 must always be used for buttons.

The correct value depends on the role, context, contrast requirements, and theme.

But having a set of heuristics gives you a much better starting point.

Light-mode backgrounds

For light interfaces, the lower end of the ramp is generally where I look for subtle backgrounds and surfaces.

A useful starting point is:

0–100 → subtle backgrounds
200 → pressed or stronger surface states

For example:

surface.subtle → gray-100
surface.hover → gray-100
surface.pressed → gray-200

These values create hierarchy without overwhelming the interface.

But again, these are starting points rather than absolute rules.

Light-mode text

Text usually moves toward the darker end of the ramp.

A useful range is:

700–950

for readable text in light mode.

For example:

text.primary → gray-950
text.secondary → gray-700
text.tertiary → gray-600

However, the important part is not simply choosing a darker number.

Always verify contrast.

A color can look sufficiently dark while still failing the required contrast ratio for its intended text size and weight.

The ramp gives you the options.

The contrast audit tells you which option is actually acceptable.

Dark-mode backgrounds

Dark interfaces reverse the basic relationship.

Instead of reaching toward the lightest steps for surfaces, you generally move toward the darker end of the ramp:

800–950

For example:

surface.default → gray-950
surface.elevated → gray-900
surface.strong → gray-800

This gives you a structured way to create depth within a dark interface without relying on arbitrary colors.

Dark-mode text

Text then moves toward the lighter end:

0–200

For example:

text.primary → gray-0
text.secondary → gray-200

Again, contrast needs to be verified.

A visually pleasing dark theme can still have poor accessibility if the foreground and background relationships are not tested.

Brand Accent: Where the Color Becomes Expression

Brand colors are slightly different.

A useful starting point for the core expression of a hue is:

400–600

This is often where the color feels most like itself.

For example:

blue-500
green-500
purple-500
orange-500

can represent the central expression of their respective hues.

From there, lighter and darker steps can support different contexts:

300 → lighter expression
400 → lighter interactive/accent state
500 → core expression
600 → stronger expression
700 → darker expression

The exact mapping depends on the palette.

The important thing is that the ramp gives the brand color a range of expression, rather than forcing the entire product to use one hexadecimal value.

Interactive States: Use the Ramp as a System

Interactive states are another place where primitive ramps become extremely useful.

Instead of inventing a completely different color for every state, use adjacent steps to create progression.

A simple starting model is:

Base    → 500
Hover → 600
Pressed → 700

The relationship communicates increasing interaction.

For example:

button.primary.default → blue-500
button.primary.hover → blue-600
button.primary.pressed → blue-700

This is much more systematic than manually choosing three unrelated colors.

The same principle can be extended to other states:

default
hover
pressed
focus
disabled
selected

But do not assume every state must simply move one step darker.

Some states, especially focus and disabled states, may require completely different treatment because their accessibility and interaction requirements are different.

The primitive ramp is a tool for creating consistency, not a substitute for judgment.

The Mental Model I Use

The easiest way to think about primitives is this:

  1. Primitives describe the material.

2. Semantic tokens describe the meaning.

3. Components describe the interface.

So the hierarchy becomes:

Primitive

Semantic Token

Component Token

Component

Product

For example:

blue-600

action.primary.background

button.primary.background

Button

Product UI

This separation is what allows a design system to scale.

If the brand color changes, you should not need to hunt through hundreds of components looking for hex values. You change the primitive or semantic mapping. The components inherit the change. That is the real power of a token architecture.

The Rule

I do not think designers should spend their time manually selecting primitives everywhere.

Instead, primitives should sit quietly underneath the system.

I reference them primarily when I am:

  1. Creating or extending semantic tokens
  2. Auditing accessibility and contrast
  3. Communicating design specifications
  4. Extending the primitive palette

Everything else should happen at a higher level.

The goal is not to make designers think about primitives more.

It is actually the opposite.

A good primitive system allows designers to think about primitives less.

You should be thinking about intent:

Is this primary text? Is this a subtle surface? Is this an interactive state? Is this destructive? Is this selected? Is this disabled?

Then the system should determine which primitive makes sense underneath.

That is the difference between a collection of colors and a real design token system.


When to Reference Primitives in a Design System was originally published in Bootcamp on Medium, where people are continuing the conversation by highlighting and responding to this story.

添加评论
点赞收藏
点踩分享查看原文
评论
?
参与讨论