Get started

Component tokens vs semantic tokens: when to add a component layer

Keep a decision semantic when the same intent should change across several components. Add a component token only when a named component property needs separate change authority and its own lifecycle, with clear surfaces it should affect and others it must leave alone. A component-flavored name doesn't justify another alias.

Updated September 7, 2026

The difference is change scope, not specificity

Semantic tokens express reusable intent. Names such as color.action.primary, color.text.muted, or space.control.inline describe a decision that several consumers may share. A button, link, tab, and selected navigation item can all consume the same action role when they are meant to move together.

Component tokens express a contract owned by a component or component family. A token such as button.background.primary.hover says the button's primary hover background may need to change without redefining primary action behavior everywhere else. That independence is what matters. The extra alias implements it.

Primitive, core, semantic, decision, theme, contextual, and component are conventions. There is no universal standard behind them. Calcite documents a core-to-semantic-to-component hierarchy, while Figma teaches several possible relationships and acknowledges that a component layer may be unnecessary. The NYS Design System documents primitive, semantic, and theme layers without making component tokens a required tier. Copying one diagram into another organization doesn't settle the boundary.

Don't infer a contract from the token name

button-primary-background may still be a redundant restatement of a shared primary action role. Conversely, a broadly named semantic token may hide behavior that only one component needs. Inspect consumers and expected propagation before judging the abstraction.

Semantic tokenComponent token
MeaningReusable intent shared across suitable consumersA property or state governed by a component contract
Reuse radiusSeveral components or surfaces with the same meaningOne component or a deliberately defined component family
Change authorityFoundation, theme, or system-level ownerComponent owner or maintainers of its public customization contract
StatesShared state intent such as focus, danger, or disabledA component's state-specific treatment when it must diverge
ModesUsually remaps shared intent across light, dark, or theme modesMay reference mode-aware semantics or define a documented component exception
Override surfaceBroad by designNarrow by design
Expected propagationEvery named consumer of that intent should changeOnly the named component consumers should change
Maintenance costFewer aliases, but a poor abstraction can create excessive blast radiusMore mappings and documentation, justified only by independent behavior
The practical boundary between semantic and component tokens

Ask whether the component owns an independent decision

Start with one disputed visual decision, not a plan to create a complete component-token tier. Suppose a primary button currently uses the shared color.action.primary role for its default background. Someone proposes button.background.primary. Before adding it, write down what should change during the next meaningful design revision.

  • Named consumers: which button variants and product surfaces use the proposed contract?
  • Independent behavior: what plausible component change should not alter links, tabs, selected navigation, or other action surfaces?
  • Modes: should the alias resolve through the same semantic role in light and dark, or is mode-specific divergence actually required?
  • States: are default, hover, focus, disabled, and destructive treatments governed by shared intent or by the button contract?
  • Protected surfaces: which unrelated consumers must remain unchanged when the component contract changes?
  • Owner: who decides whether a mismatch is fixed in the semantic role, the component alias, the implementation, or an exception?

A future possibility isn't enough by itself. Most components could diverge someday. The useful threshold is a present, named need or a supported customization contract. Without one, every extra alias expands the graph that contributors must trace, review, migrate, and keep consistent across modes.

Use the smallest boundary that matches the authority

If several form controls share the same independently governed treatment, a family-level contract may be more accurate than separate input, select, checkbox, and radio aliases. The boundary should follow the decision owner and expected propagation, not the number of component files.

Use a decision matrix with five possible outcomes

The choice isn't limited to semantic or component. A disputed token may reveal a bad semantic abstraction, duplicated component aliases, or missing evidence. Choose the disposition that fixes the actual boundary.

DispositionUse whenRequired evidenceMain risk
Keep semanticKeep semanticAll named consumers should continue to move togetherShared meaning, aligned modes and states, and no legitimate protected consumerA later component exception may require a deliberate split
Split semanticSplit semanticOne semantic role combines meanings that only happen to share a value todayTwo reusable intents with different expected consumers or future changesCreating narrowly named semantics that merely disguise component scope
Introduce componentIntroduce componentA component property has independent behavior or a supported override contractNamed consumers, protected surfaces, owner, modes, states, and a passing propagation checkAlias proliferation and hidden mode divergence
Merge component aliasesMerge component aliasesExisting component tokens always move together and have no separate owners or contractsDependency map plus observed shared propagationRemoving a distinction that a supported consumer still relies on
Block pending evidenceBlock pending evidenceExpected consumers, protected surfaces, ownership, or mode behavior are unknownAn unresolved field and a named person responsible for resolving itTreating delay as a silent permanent decision
Choose the disposition supported by current evidence

An overloaded semantic role is easy to miss. Imagine color.action.primary drives both primary controls and selected navigation, but navigation now needs a quieter color across several navigation components. The answer may be a reusable color.navigation.selected role, rather than a button token or a one-off override. The new boundary remains semantic because the intent is still shared beyond one component.

Inspect a real semantic source before designing the component layer

Browse published kits to see which upstream roles and modes a consuming project can map. Then create component aliases only where your application has evidence for an independent contract.

Trace the alias path before adding another hop

An alias path makes the proposed contract reviewable. The following names are illustrative project conventions, not Identity Forge output or a universal DTCG structure.

{
  "color": {
    "blue": {
      "600": {
        "$type": "color",
        "$value": "#2457D6"
      }
    },
    "action": {
      "primary": {
        "$type": "color",
        "$value": "{color.blue.600}"
      }
    }
  },
  "button": {
    "background": {
      "primary": {
        "$type": "color",
        "$value": "{color.action.primary}"
      }
    }
  }
}
Illustrative alias path: primitive value to shared semantic intent to a proposed component contract.

If button.background.primary always equals color.action.primary, has no independent owner, exposes no supported override, and should change whenever the semantic role changes, it adds another name but no separate decision. Keep the button mapped directly to the semantic token.

The component alias earns its place when the component contract can change independently. For example, a product may decide that primary buttons need a distinct hover treatment while text links and selected navigation continue using the shared action role. The component token can still reference a semantic value by default. Its purpose is to provide a narrow override point with documented ownership, even when there is no visual difference on day one.

A component token doesn't need a unique value

Two aliases may resolve to the same value today and still represent different contracts. Keep them separate only if independent ownership or expected future changes are concrete and documented. Coincidental equality proves neither that they should merge nor that they should remain separate.

Run a controlled two-change propagation test

A diagram predicts reach. A controlled change shows where the current implementation actually reaches. Record the expectation before editing, so a surprising result can't be reinterpreted as success afterward. Run the test in both relevant modes and representative states. Use reversible test values that make propagation easy to see, then restore the accepted values through your normal workflow.

  1. 1

    Freeze the boundary under review

    Name the source decision, current alias path, modes, states, intended consumers, protected surfaces, owner, and proposed disposition. Mark unknown fields as unresolved rather than guessing.

  2. 2

    Write the expected result for the semantic change

    Choose one shared semantic role. List every representative consumer that should change and every surface outside that intent that should not. Do this before changing a value.

  3. 3

    Apply one temporary semantic change

    Change the semantic role at its authoritative source, regenerate or remap through the project's normal path, and inspect the named consumers in each relevant mode and state. Record observations separately from expectations.

  4. 4

    Write the expected result for the component change

    If a component boundary is still justified, state which component consumers should change and which semantic peers are protected. Include any supported downstream override surface.

  5. 5

    Apply one temporary component change

    Change only the proposed component contract. Verify that its named consumers move and that links, navigation, other controls, and other protected surfaces do not. Record missing, extra, and mode-specific changes.

  6. 6

    Choose a disposition from the evidence

    Keep semantic, split semantic, introduce component, merge redundant component aliases, or block pending evidence. Restore accepted values and retain the observations with the decision record.

A semantic test fails when an intended consumer doesn't move, an unrelated consumer changes, or light and dark modes follow different undocumented paths. A component test fails when the component doesn't respond, the change leaks into protected surfaces, or a local override masks the alias. These failures don't automatically mean another token is needed. First, locate the layer that owns the mismatch.

Don't promote an expectation into evidence

If the test hasn't been run, write expected, illustrative, or unresolved. A proposed alias graph is a design decision under review, not proof that a consuming application maps or renders it correctly.

Keep Identity Forge on the upstream side of the boundary

Identity Forge supplies semantic light and dark roles through its design-kit exports, including DTCG, CSS, Tailwind, and shadcn formats. Those roles are upstream inputs. The consuming application owns any component aliases, mappings, exceptions, component APIs, and evidence that a change reached the correct surfaces.

Token specimen · real values

Ambient Sage

Live render

Ambient Sage's actual tokens — the same values its exports use.

Color tokensSemantic roles with HEX / HSL / CMYK

Color tokens

Ambient Sage
light · HEX · HSL · CMYK

Core

#F3F4EF

background

H 72 · C0, 0, 2, 4

#1A1C17

foreground

H 84 · C7, 0, 18, 89

#E5E6E0

card

H 70 · C0, 0, 3, 10

#ECEEE8

muted

H 80 · C1, 0, 3, 7

#D8D9D2

border

H 68.57 · C0, 0, 3, 15

Brand

#FEE951

primary

H 52.72 · C0, 8, 68, 0

#1A1C17

primary-fg

H 84 · C7, 0, 18, 89

#E5E6E0

secondary

H 70 · C0, 0, 3, 10

#F7E464

accent

H 52.24 · C0, 8, 60, 3

#FEE951

ring

H 52.72 · C0, 8, 68, 0

Semantic

#C0392B

destructive

H 5.64 · C0, 70, 78, 25

#FFFFFF

destructive-fg

H 0 · C0, 0, 0, 0

#2D7238

success

H 129.57 · C61, 0, 51, 55

#C97D12

warning

H 35.08 · C0, 38, 91, 21

#545651

muted-fg

H 84 · C2, 0, 6, 66

Charts

#FEE951

chart-1

H 52.72 · C0, 8, 68, 0

#4A8FD4

chart-2

H 210 · C65, 33, 0, 17

#6BBF8A

chart-3

H 142.14 · C44, 0, 28, 25

#E07498

chart-4

H 340 · C0, 48, 32, 12

#E8A24B

chart-5

H 33.25 · C0, 30, 68, 9

Ambient Sage shows a published semantic color source in light mode. It does not define or validate a consuming application's component-token layer.

For an Ambient Sage consumer, primary, primary-foreground, border, or ring can enter the project as semantic inputs. A proposed alias such as button.background.primary remains hypothetical until the application defines it. Whether that alias should exist depends on the application's button contract, not on the source kit having a primary role.

Copy this token-boundary decision record

Keep one compact record beside the token source or contribution proposal. Its storage format is an organizational choice. What matters is keeping expectations, observations, authority, and unresolved evidence distinct.

decision: "Should primary button background get a component token?"
status: "candidate"
source_authority: "design-system semantic token source"
source_decision: "color.action.primary"
current_alias_path:
  - "color.blue.600"
  - "color.action.primary"
proposed_alias: "button.background.primary"
modes:
  light: "mapped; observation pending"
  dark: "mapped; observation pending"
states:
  default: "in scope"
  hover: "in scope"
  focus: "ring governed separately"
  disabled: "out of scope; uses disabled intent"
named_consumers:
  - "PrimaryButton"
  - "ConfirmButton"
protected_surfaces:
  - "text links"
  - "selected navigation"
  - "primary tabs"
expected_semantic_change:
  - "all named action-primary consumers change"
expected_component_change:
  - "only named primary button consumers change"
observations:
  semantic_change: "not run"
  component_change: "not run"
  protected_surfaces: "not inspected"
exceptions: []
evidence_status: "pending_evidence"
owner: "component-system maintainer"
unresolved:
  - "Does dark-mode hover require independent behavior?"
  - "Is the alias a supported consumer override?"
disposition: "block pending evidence"
Illustrative record. Replace every project-specific name and pending observation with evidence from the consuming system.

The record makes the choice reversible. Another maintainer can see why the boundary was proposed, what would falsify it, and whether the decision is settled. A candidate or pending_evidence status must remain open. It isn't a weaker spelling of approved.

Diagnose the common boundary failures

  • Component aliases merely rename shared intent: several component tokens point to one semantic role, always move with it, and have no separate owners or override contracts. Consider mapping components directly to the semantic role or merging redundant aliases.
  • A semantic token encodes one component: a supposedly reusable name describes button placement, input borders, or another narrow consumer. Move it to component scope if the behavior and authority are genuinely local.
  • One semantic name hides two reusable meanings: consumers share a value today but should have different propagation. Split the semantic intent before reaching for per-component tokens.
  • Modes diverge without a declared rule: light mode follows the alias graph while dark mode contains a local value. Repair the mapping or document a real mode exception.
  • Local overrides mask the contract: selectors, utility classes, inline values, or detached component copies prevent the token change from reaching a consumer. Fix the implementation before redesigning the token hierarchy.
  • Aliases form a cycle or an untraceable chain: no layer can be identified as authoritative. Break the cycle and give each decision one direction of dependency.
  • No one owns correction: foundation, theme, and component maintainers each expect another layer to fix a mismatch. Assign the owner before adding another layer.

The costliest failure is token proliferation without correction authority. A large graph can look mature while making every change harder to explain. Review the distinct contracts rather than the number of aliases. Each layer should answer a separate question and have someone responsible for that answer.

Make the decision on one disputed token

Choose one token whose scope is disputed. Name its consumers and protected surfaces, run the semantic change, then run the proposed component change if the first result supports it. Record what actually moved in each mode and state. Finish with one explicit disposition: keep, introduce, merge, split, or block.

Component and semantic token questions

Should every component have component tokens?

No. A component can consume semantic tokens directly when its styling expresses shared intent. Add component tokens only for independently governed behavior, a supported customization surface, or another present contract that justifies the extra mapping.

Can a component token reference a semantic token?

Yes. That is a common and useful alias path. The component token can inherit the semantic value by default while preserving a narrow point for an authorized component-level change.

Does a different value prove that a component token is needed?

No. A different value may reveal an overloaded semantic role, a legitimate component exception, or an implementation bug. Decide based on meaning, consumers, ownership, and expected propagation rather than value inequality alone.

What if the team cannot identify protected surfaces?

Block the decision pending evidence. Map representative consumers before changing the hierarchy. Without protected surfaces, the team cannot test whether the proposed component boundary isolates anything.

Are component tokens generated by Identity Forge?

No supported product fact establishes that Identity Forge generates component tokens. Its semantic light and dark exports are upstream inputs. The consuming application owns component aliases, mappings, exceptions, and downstream verification.

Sources