Skip to content

Naming Conventions

Tokens are organized into three tiers. Each tier has a distinct purpose, its own naming convention, and a clear dependency direction: higher tiers reference lower tiers, never the reverse. Every tier starts with the token category (color, spacing, radius, opacity, typography, shadow, duration, zindex, ...).

TierPurposeConventionExample
BaseRaw values without intentDescriptive, type-groupedcolor.palette.blue-500
SemanticIntent-based, themeableSix-layer pathcolor.action.brand.hover
ComponentUI-mapped, per-componentFive-layer pathcolor.button.primary.background.hover

The reference chain flows in one direction:

Component tokens → Semantic tokens → Base tokens

Base tokens are the raw building blocks. They represent values without intent and use neutral, descriptive names grouped under a type-specific namespace:

CategoryBase groupExample tokens
Colorcolor.palettecolor.palette.blue-500, color.palette.gray-900, color.palette.white
Spacingspacing.scalespacing.scale.xs, spacing.scale.sm, spacing.scale.md
Radiusradius.scaleradius.scale.none, radius.scale.sm, radius.scale.md, radius.scale.full
Opacityopacity.scaleopacity.scale.0, opacity.scale.50, opacity.scale.100
Shadowshadow.rawshadow.raw.sm, shadow.raw.md, shadow.raw.lg
Typographyfont.*font.family.sans, font.size.lg, font.weight.bold
Durationduration.scaleduration.scale.fast, duration.scale.normal, duration.scale.slow
Z-Indexzindex.scalezindex.scale.dropdown, zindex.scale.modal, zindex.scale.toast

(And more — common categories include breakpoints, easing, motion, sizing depending on your system.)

Base tokens sit outside the six-layer semantic convention. Semantic tokens reference them via {color.palette.blue-500} style aliases.

Every semantic token path is a subsequence of six ordered layers:

category.concept.sentiment.prominence.state.scale
LayerPurposeExample values
categoryToken type or domaincolor, spacing, typography, shadow
conceptWhat it’s used fortext, background, surface, action, border, icon, overlay, gap, inset, heading, body, elevation
sentimentSemantic intent or roleneutral, brand, danger, success, warning, info
prominenceVisual weight or emphasismuted, subtle, strong, inverse
stateInteraction statehover, active, focus, disabled, selected
scaleSize or intensity stepxs, sm, md, lg, xl, 2xl

Each layer has its own distinct vocabulary — values never overlap between layers. muted is always prominence. danger is always sentiment. hover is always state. md is always scale. This means layers can be omitted when they’re not needed, and there’s never ambiguity about which layer a segment belongs to.

Layers can be skipped, but they can never be reordered. Every token path must be a subsequence of the full six-layer order — broadest classification first, most specific last.

color.text.danger.muted is valid (sentiment then prominence). color.text.muted.danger is not (prominence before sentiment — going backwards).

Most tokens use two to four layers:

Token pathLayers used
color.textcategory + concept
color.text.mutedcategory + concept + prominence
color.text.dangercategory + concept + sentiment
color.text.muted.disabledcategory + concept + prominence + state
color.background.danger.subtlecategory + concept + sentiment + prominence
color.action.brand.hovercategory + concept + sentiment + state
spacing.gap.mdcategory + concept + scale
typography.heading.lgcategory + concept + scale

Different token categories lean on different subsets:

  • Color tokens typically use concept through state and skip scale.
  • Spacing, typography, and shadow tokens typically use concept and scale, skipping sentiment, prominence, and state.

The convention is the same — each category uses the subset it needs.

Token pathCategoryConceptSentimentProminenceStateScale
color.textcolortext
color.text.mutedcolortextmuted
color.text.dangercolortextdanger
color.text.danger.mutedcolortextdangermuted
color.text.muted.disabledcolortextmuteddisabled
color.backgroundcolorbackground
color.background.danger.subtlecolorbackgrounddangersubtle
color.surfacecolorsurface
color.action.brandcoloractionbrand
color.action.brand.hovercoloractionbrandhover
color.action.danger.strong.activecoloractiondangerstrongactive
color.bordercolorborder
color.border.focuscolorborderfocus
color.icon.successcoloriconsuccess
spacing.gap.mdspacinggapmd
spacing.inset.lgspacinginsetlg
typography.heading.lgtypographyheadinglg
typography.body.smtypographybodysm
shadow.elevation.mdshadowelevationmd
  1. Intent over implementation. color.action.brand instead of color.blue-500. When your brand color changes from blue to purple, you rename zero tokens.
  2. Predictability. If color.text exists, a developer can guess that color.text.muted and color.text.subtle probably do too.
  3. Orthogonal layers. Sentiment, prominence, state, and scale answer different questions: “what role?”, “how loud?”, “what interaction?”, and “what size?” Keeping them separate prevents naming collisions and makes the system composable.
  4. No going back. Layers always appear in the canonical order. You can skip, but you can’t reorder. This keeps every path unambiguous.

Each layer becomes a nesting level in the JSON structure:

{
"color": {
"text": {
"$type": "color",
"$root": { "$value": "{color.palette.gray-900}" },
"muted": {
"$root": { "$value": "{color.palette.gray-500}" },
"disabled": { "$value": "{color.palette.gray-300}" }
},
"danger": { "$value": "{color.palette.red-500}" }
},
"action": {
"$type": "color",
"brand": {
"$root": { "$value": "{color.palette.blue-500}" },
"hover": { "$value": "{color.palette.blue-600}" }
}
}
},
"spacing": {
"gap": {
"$type": "dimension",
"sm": { "$value": "{spacing.scale.sm}" },
"md": { "$value": "{spacing.scale.md}" },
"lg": { "$value": "{spacing.scale.lg}" }
}
}
}

When a node needs to be both a token and a group (e.g. color.text.muted has a value and a child disabled), use $root to hold the node’s own value while allowing children alongside it.

Dispersa flattens this structure during parsing, so color.text.muted and color.action.brand.hover are the token paths you see in build output.

Component tokens sit above the semantic layer. They map semantic tokens to specific UI components, creating a stable contract between design and implementation. Like the other tiers, they start with the token category. Each component token path is a subsequence of six ordered layers:

category.component.variant.slot.property.state
LayerPurposeExample values
categoryToken type or domaincolor, spacing, typography, shadow
componentUI component namebutton, card, input, badge, tooltip
variantOptional variant or roleprimary, secondary, ghost, success, danger
slotOptional component particon, avatar, badge, label, indicator (or any custom slot name)
propertyCSS-ish concernbackground, text, border, padding, shadow, radius, gap
stateOptional interaction statehover, active, focus, disabled, selected

The same ordering rule applies: layers can be skipped but never reordered. Variant, slot, and state are optional; category, component, and property are required.

Semantic concepts (text, background, action, border…) and component names (button, card, input, badge…) are naturally distinct vocabularies, so there is no ambiguity when both live under the same category namespace like color.*.

Token pathCategoryComponentVariantSlotPropertyState
color.button.primary.backgroundcolorbuttonprimarybackground
color.button.primary.background.hovercolorbuttonprimarybackgroundhover
color.button.primary.icon.backgroundcolorbuttonprimaryiconbackground
color.button.secondary.bordercolorbuttonsecondaryborder
color.button.danger.backgroundcolorbuttondangerbackground
color.card.backgroundcolorcardbackground
spacing.card.avatar.paddingspacingcardavatarpadding
color.input.bordercolorinputborder
color.input.border.focuscolorinputborderfocus
color.input.danger.bordercolorinputdangerborder
color.badge.success.backgroundcolorbadgesuccessbackground

Component tokens follow the same nesting pattern, with category as the outermost level. A single component’s tokens may span multiple categories:

{
"color": {
"button": {
"primary": {
"background": {
"$type": "color",
"$root": { "$value": "{color.action.brand}" },
"hover": { "$value": "{color.action.brand.hover}" },
"active": { "$value": "{color.action.brand.active}" }
},
"text": {
"$type": "color",
"$value": "{color.text.inverse}"
}
}
},
"input": {
"border": {
"$type": "color",
"$root": { "$value": "{color.border}" },
"focus": { "$value": "{color.border.focus}" }
}
}
},
"spacing": {
"button": {
"primary": {
"padding": {
"$type": "dimension",
"$value": "{spacing.gap.md}"
}
}
}
}
}

Component tokens should reference semantic tokens, not base tokens directly. This keeps the dependency chain clean and ensures theming works end-to-end:

color.button.primary.background → {color.action.brand} (semantic)
color.action.brand → {color.palette.blue-500} (base)

Skipping the semantic layer (e.g., color.button.primary.background → {color.palette.blue-500}) creates a shortcut that bypasses theming — when the theme changes, that component token won’t update.