Specific tokens at component level
How to decide when semantic tokens fall short and when adding a component-specific layer actually makes your system simpler, not more complex.
Design Tokens Pills · Issue #28
There’s a question that comes up in almost every design system at some point:
“Should we create tokens specifically for this component?”
The forever answer is “it depends.” Which is true, but not very useful.
In this issue, we’ll try to make that decision concrete, with clear criteria, real examples, and an honest look at when component tokens help.
A quick recap: the three-tier architecture
Before we get into it, a fast recap of the three-tier token architecture:
Primitives/Global — raw values never used directly in components.
Semantic/Alias — intent-based aliases, used in most components.
Component-specific — scoped to a single component.
The question is when the third level earns its place.
When semantic tokens are enough
In most cases, semantic tokens do the job well. If a component just maps one semantic value to one property, adding a component layer in between is pure overhead.
/* Semantic is enough — no need for a component token */
.tag {
background-color: var(--color-surface-neutral);
color: var(--color-text-primary);
border-radius: var(--border-radius-full);
}The tag component is just consuming shared values. If color-surface-neutral changes system-wide, the tag updates too. There’s nothing unique about how this component uses that value, so there’s no reason to introduce a layer like tag.background.
💡 Rule of thumb: if a semantic token name already answers “what this value is for” in a way that makes sense inside your component, you don’t need a component token.
When you do need component tokens
1. The component has a unique value that doesn’t belong to the semantic scale
Sometimes a component needs a specific value that doesn’t exist in the semantic layer and shouldn’t, because it is too specific to generalize.
A classic example: a tooltip’s background is often a very dark color, but it’s not the same as color.surface.overlay or color.background.inverse. It’s specific to the tooltip’s visual role.
{
"tooltip": {
"background": {
"$value": "{color.neutral.900}",
"$type": "color",
"$description": "Distinct from other dark surfaces. Do not reuse outside tooltip."
},
"text": {
"$value": "{color.neutral.50}",
"$type": "color"
},
"padding-x": {
"$value": "{spacing.3}",
"$type": "dimension"
},
"padding-y": {
"$value": "{spacing.1-5}",
"$type": "dimension"
}
}
}Component tokens act as named, documented decisions. Anyone reading tooltip.background immediately understands the scope and intent.
2. The same semantic value is used differently across variants
This is where things get interesting. Imagine a button component with a primary and a ghost variant. Both might reference color.action.background but for completely different reasons, and with completely different override behaviors.
Without component tokens, any change to color.action.background affects both variants simultaneously. That might be exactly what you want or it might break one of them.
{
"button": {
"primary": {
"background": { "$value": "{color.action.background}", "$type": "color" },
"background-hover": { "$value": "{color.action.background-hover}", "$type": "color" },
"text": { "$value": "{color.text-on-action}", "$type": "color" }
},
"ghost": {
"background": { "$value": "transparent", "$type": "color" },
"background-hover": { "$value": "{color.action.background}", "$type": "color" },
"text": { "$value": "{color.action.background}", "$type": "color" }
}
}
}Both ghost.text and ghost.background-hover reference color.action.background but they can be overridden independently when needed (e.g., in a dark theme or different product brand) without touching the primary variant.
3. You need to support theming at the component level
This is the strongest case for component tokens. When your system needs to support multiple brands or themes, the component token layer is what makes per-component overrides possible without duplicating the component itself.
/* Base theme */
{
"card": {
"background": { "$value": "{color.surface.default}", "$type": "color" },
"border": { "$value": "{color.border.subtle}", "$type": "color" },
"border-radius": { "$value": "{border-radius.md}", "$type": "dimension" }
}
}
/* Brand B theme — overrides only what changes */
{
"card": {
"background": { "$value": "{color.surface.warm}", "$type": "color" },
"border-radius": { "$value": "{border-radius.sm}", "$type": "dimension" }
}
}Brand B only needs to redefine the two values that differ. Everything else cascades from the base. Without the component token layer, you’d have no clean way to express these per-brand overrides.
The token bloat problem
Component tokens are useful, but they scale quickly. A design system with 40 components, each with 10–15 component tokens, ends up with 400–600 tokens at this level alone. That’s not automatically a problem but it is if those tokens aren’t providing real value.
Signs you’ve gone too far include:
Component tokens that just repeat semantic tokens with a new name.
button.primary.background → color.action.backgroundwith no intermediate logic adds a layer with no benefit.Component tokens for values that never change between themes or variants. If
badge.border-radiusis always{border-radius.full}and never overridden, it’s noise.One component token per CSS property, even for properties that could share a semantic token. Not every property needs its own token.
A useful filter:
"Would I ever need to override this value independently, at the component level, without changing the semantic token?"
If the answer is no, just skip it.
Ask some questions
Here’s a simple set of questions to run through when you’re deciding whether to create a component token:
“Is this value unique to this component and doesn’t belong in the semantic scale?”
Yes → Create a component token
“Does this component have variants or states that use the same semantic value differently?”
Yes → Create component tokens per variant
”Does your system support multiple themes or brands?”
Yes → Component tokens are your override layer
“Is this value always the same, across all themes and variants?”
No → Component token needed
“Is the semantic token name already clear enough to use directly in this component?”No → Component token needed
Naming component tokens
If you decide to create component tokens, naming consistency is critical. A reliable pattern:
{component}.{variant}.{property}.{state}
Where {variant} and {state} are optional and only included when relevant.
Some practical naming examples:
button.primary.background ✅
button.primary.background.hover ✅
button.background.primary.default ❌ (inconsistent order)
btn-bg-primary ❌ (abbreviations, no structure)Keep the component name first. This makes tokens easier to group, scan, and scope in your tooling. Skip the state suffix when there’s only one state: button.primary.background is cleaner than button.primary.background.default
Not sure which token level to use for a component in your system?
Share it in the comments or the subscribers chat.


