Primitive vs semantic tokens: the difference, with examples
Primitive tokens store raw values; semantic tokens name what a value is for and point to a primitive. Examples for color, spacing and type in Figma and Tailwind.
A primitive token holds a raw value with no opinion about where it's used, like blue/600 or space/4. A semantic token names a job in the interface, like action/primary or space/inset/md, and points to a primitive instead of holding its own value. Your designs and components use the semantic layer; the primitive layer is the shelf it picks from.
That's the whole distinction. The rest of this article is about what it means in practice: what goes in each layer beyond color, when a third layer (component tokens) is worth it, and the mistakes that make a two-layer system behave like a one-layer one. For how to name semantic color tokens for light and dark themes, see our separate naming reference.
The layers side by side
| Layer | Example name | Example value | Who changes it | When it changes |
|---|---|---|---|---|
| Primitive | blue/600 | #2563eb | Whoever owns the brand palette | Rebrand, new palette |
| Primitive | space/4 | 16 | Design system owner | Almost never |
| Semantic | action/primary | → blue/600 | Design system owner | Theme, product decision |
| Semantic | space/inset/md | → space/4 | Design system owner | Density change |
| Component | button/primary/background | → action/primary | Component owner | One component diverges |
Other systems use other words for the same layers. Adobe's Spectrum calls primitives "global" tokens and talks about "component-specific" tokens for the third layer. Figma's design tokens guide notes that semantic tokens are sometimes called alias tokens. If a team says "core", "base", "reference" or "global", they almost always mean primitives.
What makes a token primitive
A primitive answers "what values exist?" It's a complete, boring list:
- Every shade of every color family you allow.
- Every step of the spacing scale.
- Every radius, font size and line height you're willing to ship.
The test is that the name says what the value is, not what it's for. gray/100 and radius/8 pass. card-background fails, because it already describes a use.
Primitives rarely change after the first few weeks. When they do, it's usually a rebrand, and that's exactly when you want one edit in one place.
What makes a token semantic
A semantic token answers "what is this for?" Spectrum's docs put it well: semantic tokens communicate purpose and intent rather than describing values. In practice that means:
text/secondary, notgray/600.space/stack/lg, notspace/6.font/body/size, notfont/16.
Each one points at a primitive. The pointer is the decision. If the team decides secondary text should be a shade darker, you move the pointer from gray/600 to gray/700 once, and every screen follows.
The semantic layer is also where context lives. A semantic color can point at one primitive in a Light mode and another in a Dark mode; a spacing token can point at a smaller step in a compact density. Primitives stay the same in every context.
Semantic vs component tokens
Component tokens are a third, optional layer: button/primary/background, input/border/focus, tooltip/max-width. They point at semantic tokens and exist so one component can change without touching anything else.
Spectrum is explicit that it prefers reusable semantic decisions and keeps component-specific tokens to a small role. I agree for most product teams. A component token earns its place when:
- the component genuinely needs a value the semantic layer doesn't offer, or
- several products share the component and need to restyle it separately.
If every component gets its own tokens on day one, you've tripled the number of variables and made a rebrand harder, because now there are three layers to trace instead of two.
Two layers or three?
Start with two. Most teams who reach for three layers early end up with component tokens that alias a semantic token one-to-one and never diverge. Those add a hop without adding a decision.
Move to three when you can name the first real exceptions, or when you're building a multi-brand system where each brand restyles shared components. One way to structure that is one primitives collection per brand and one shared semantic collection on top, with brands as modes.
Primitive and semantic tokens in Figma
In Figma, each layer is usually its own variable collection.
- Create a
Primitivescollection with your raw values: colors, number variables for spacing and radius, string variables for font families. Figma's variables overview lists six variable types, and an alias can only point at a variable of the same type. - Hide primitives from pickers. Select them, open Edit variables, and clear their scopes, so people can't drop
blue/600straight onto a button. - Create a
Semanticcollection. For each value, right-click and choose Create alias, then pick the primitive. - Bind layers only to semantic variables.
Figma's own design tokens guide gives a rule I follow: aim every semantic token at a primitive, never at another semantic token. Chains like action/primary → brand/main → blue/600 look tidy, but every extra hop is somewhere a change can go missing.
Want the Light and Dark columns too? That's a modes question, and our variable modes setup guide walks through it step by step.
Primitive and semantic tokens in Tailwind
Tailwind v4 makes the split visible. Its default theme is a primitive layer: variables like --color-blue-600 and a single --spacing base value that spacing utilities multiply. You add a semantic layer in your own CSS:
@import "tailwindcss";
:root {
--text-secondary: var(--color-gray-600);
--inset-md: calc(var(--spacing) * 4);
}
@theme inline {
--color-secondary: var(--text-secondary);
--spacing-inset-md: var(--inset-md);
}Now text-secondary and p-inset-md are utilities that describe a job. Tailwind's theme docs recommend the inline option whenever a theme variable references another variable, so the utility resolves to the right value.
The mapping to Figma is direct: Primitives matches Tailwind's default theme, and your Semantic collection matches the variables you define on top of it.
Common mistakes
Primitives with intent in the name. primary-500 is a half-step. It's a primitive pretending to be semantic, and it breaks the moment the brand color changes family. Use the color family name (blue/500) for the primitive and keep "primary" for the semantic layer.
Semantic tokens that hold raw values. One hex code typed into the semantic collection and that token no longer follows the palette. Every semantic value should be an alias.
Semantic to semantic chains. Covered above: point at primitives directly.
Too many semantic tokens. If you have text/on-card-header-hover, you've written component tokens into the semantic layer. Keep semantic names general enough to reuse across components.
Binding UI to primitives "just for now". Every shortcut here is a layer that won't follow a theme change later. Scoping primitives out of the picker prevents most of it.
Where Huekit fits
Huekit is our plugin, and it generates both layers for you. Starting from brand colors, a preset palette or a code editor theme, we create the color scales as primitive variables, add semantic tokens that alias them in Light and Dark modes, and generate spacing, radius, size and type foundations alongside. Everything it creates is ordinary Figma variables you can rename and re-point. If you'd rather start from a generated structure than an empty Variables tab, you can try it from the Figma Community.
FAQ
What is the difference between primitive and semantic tokens?
Primitive tokens store raw values (blue/600 = #2563eb). Semantic tokens describe a role (action/primary) and point to a primitive. You change a primitive to change what a value is, and re-point a semantic token to change where it's used.
Are semantic tokens the same as alias tokens?
Mostly, yes. "Alias" describes how the token works (it references another token), and "semantic" describes what its name expresses. Figma's guide notes the two terms are used for the same layer.
What is the difference between semantic and component tokens?
Semantic tokens are shared decisions used across many components. Component tokens apply to one component, like button/primary/background, and usually point to a semantic token.
Do I need component tokens?
Not at first. Start with primitives and semantics, and add component tokens when a component has to diverge from the shared values or when several brands restyle the same component.
Should semantic tokens reference other semantic tokens?
No. Figma's design tokens guide recommends pointing semantic tokens straight at primitives, because chains make the system harder to trace and maintain.
Do primitive tokens change in dark mode?
No. Primitives keep one value. The semantic layer switches which primitive it points to in each mode.
Try it on one token
Open your file's Variables tab, find the color your primary buttons use, and check what it's bound to. If it's a primitive or a hex value, create one semantic variable for it, alias it to that primitive, and rebind the buttons. That one change shows you the whole pattern.
About the author
Written by the Huekit team.
Want the fast path?
Huekit builds the color scales, semantic tokens, and Light/Dark modes inside your Figma file, then exports JSON tokens or a Tailwind config.
Get Huekit on FigmaKeep reading
Accessible color palette for light and dark mode in Figma
Build an accessible color palette that meets WCAG AA in light and dark mode: the contrast ratios, the color pairs to test, and the Figma tools that check or adjust them.
Catppuccin, Dracula and Tokyo Night in Figma: turn an editor theme into variables
Map Catppuccin, Dracula, Tokyo Night or One Dark onto Figma variables with Light and Dark modes: which flavor goes where, which roles to create, and which pairs fail contrast.
How to export Figma variables to Tailwind (v4 @theme or v3 config)
Three ways to export Figma variables to Tailwind: native JSON export plus a build step, an export plugin, or a generator with export built in. With v4 CSS for light and dark.