# How to create semantic color tokens for UI

Semantic color tokens name what a color does, such as text-primary or surface-error. Map them to primitive values, document where they belong, and validate each theme and state. The same semantic name can resolve to a different value in light and dark modes.

By The Design Project. Updated 2026-10-21.

## What you will make

A role-based token map for surfaces, text, actions, and feedback.

## Before you start

- An existing palette or the palette guide
- A representative interface with feedback states
- The token format used by your implementation

## Why semantic names matter
Semantic color tokens describe what a color does rather than only what it looks like. A name such as text-secondary or surface-error gives a contributor a decision rule. A name such as gray-500 identifies a value in a palette but does not explain where it belongs.

In TDP’s Figma-to-Storybook demonstration, the token audit checks whether semantic colors actually reference primitives, identifies missing mappings, and revisits the results visually. That sequence matters: a token can have a semantic-looking name while still containing an unmanaged raw value.

Use this guide to connect a palette to one real interface. The examples are intentionally small so you can inspect the complete path from primitive value to component use.

## Step 1. Inventory colors and their jobs
List current colors alongside the elements that use them. Group by role before grouping by identical values. A destructive action and a brand accent may share a red today but should remain separately named if they communicate different things. This preserves meaning when the brand or theme changes.

## Step 2. Separate primitive values from semantic roles
A primitive describes a value in a palette; a semantic token describes its purpose. Your implementation may use CSS, JSON, or a design-token tool. Choose the format your team can maintain and avoid inventing a parallel source of truth.

```css
:root {
  --neutral-950: #242424;
  --neutral-50: #faf7f4;
  --red-800: #8c2538;
  --surface-default: var(--neutral-50);
  --text-primary: var(--neutral-950);
  --text-error: var(--red-800);
}
[data-theme="dark"] {
  --surface-default: #242424;
  --text-primary: #faf7f4;
  --text-error: #ffb7c0;
}
```

## Step 3. Specify states and pairings
Document which foreground belongs on which surface. Add state tokens when an interaction needs them, and state whether a border is decorative or necessary to identify the control. Do not assume a darker hover color or a lighter disabled color remains legible in every theme.

| Role | Pair to review | Meaning |
| --- | --- | --- |
| Primary text | Text on default and raised surfaces | Main reading content |
| Action text | Label on action background | What the button will do |
| Error text | Message on error or default surface | What failed and how to recover |
| Focus | Indicator beside adjacent colors | Current keyboard position |

## Step 4. Add a non-color explanation
An error needs text or another meaningful cue, not just a red border. A chart series may need labels, shapes, or patterns. W3C’s use-of-color guidance explains why color alone is insufficient for conveying information. Design the message and color together instead of adding a cue at the end.

## Step 5. Migrate a small slice
Replace values in one component family, inspect every supported theme, and review the result in a full page. Record intentional exceptions. Search for remaining raw values and classify them before replacing them; some may belong to imagery or a context outside the shared token system.

## Worked example: separate a value from its job
Imagine the same blue appears on the primary button and a selected row. Those roles happen to share a primitive today, but they can change independently later. Keep the semantic names distinct so a future selected-state adjustment does not automatically recolor every action.

```css
:root {
  --palette-blue-700: #1d4ed8;
  --palette-blue-50: #eff6ff;
  --color-action-primary: var(--palette-blue-700);
  --color-selection-surface: var(--palette-blue-50);
  --color-selection-text: var(--palette-blue-700);
}
```

These are illustrative CSS variables, not a complete theme or an interchange-format example. Validate the pairs in your interface and use your project’s naming and build conventions.

## Audit aliases without erasing intent
When two semantic tokens share a value, ask whether they share a responsibility. Field borders and decorative dividers may look identical in one theme while serving different purposes. A blanket deduplication can make later accessibility or theme work harder.

| Finding | What it might mean | Review action |
| --- | --- | --- |
| Semantic token stores a raw value | Alias was never connected | Confirm the primitive and replace the mapping |
| Two roles share a primitive | Intentional reuse | Keep both names if their responsibilities differ |
| A role has no dark-mode mapping | Incomplete theme support | Define and test the missing pair |
| A tinted value uses opacity | Appearance depends on the background | Inspect the composite result |
| A component uses a primitive directly | Missing or bypassed semantic role | Decide whether a role is needed |

Use the [Design Tokens format reference](https://www.designtokens.org/tr/2025.10/format/) when exchanging typed token data between tools. Your runtime CSS can remain a separate generated or maintained representation. Do not imply that a project-specific JSON object automatically conforms to that format.

## Document foregrounds and surfaces together
A background token alone is incomplete guidance for a filled control or feedback message. Document the intended foreground, border where relevant, and interaction states. A consumer should not have to improvise a label color for every surface.

The same principle applies to themes. Keep the role stable while allowing its value to change. Review the role in the same component across supported modes, including focus and error presentation. A token name cannot guarantee the resulting pair is readable.

## Introduce the map through one component
Replace the approved values in a small component, then compare its states before and after. Inspect the implementation for raw values and verify that changing the semantic mapping reaches the intended consumers. This catches aliases that exist in documentation but are not actually used.

Keep changes reviewable. Migrating every color in the product at once makes it difficult to identify which mapping caused a regression. Preserve a record of unresolved values and expand once the pilot works.

## Maintain semantic roles as product decisions
Create a new role when it expresses a meaningful responsibility, not merely because a new screen needs a slightly different shade. Ask whether an existing role fits and whether the exception belongs at the component level. Explain intentional exceptions so agents and teammates do not copy them indiscriminately.

For the palette itself, use [create a UI color palette](https://designproject.io/resources/guides/create-ui-color-palette/). For the larger relationship between tokens, components, and agent context, use [build an AI-ready design system](https://designproject.io/resources/guides/build-ai-ready-design-system/).

## Completion checklist
- Semantic names describe intent.
- Theme mappings preserve meaning.
- Foreground/background pairs are documented.
- Status includes a cue beyond hue.
- Migration is checked in component and page context.

## Common questions
### Should two identical values share one semantic token?
Only when they share a role. Separate roles can reference the same primitive without becoming the same token.
### Is every border required to meet 3:1 contrast?
No. W3C’s non-text contrast criterion depends on whether the visual information is needed to identify a component or state. Evaluate the boundary’s purpose and the criterion’s exceptions.

## Sources and TDP resources
- [W3C: use of color](https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html)
- [W3C: non-text contrast](https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html)
- [TDP’s color design article](https://designproject.io/blog/color-graphics-the-power-of-color-in-graphic-design/)


## Related guides

- [How to create a UI color palette, step by step](https://designproject.io/resources/guides/create-ui-color-palette/)

- [How to check color contrast in a UI](https://designproject.io/resources/guides/check-color-contrast/)

- [How to build an AI-ready design system](https://designproject.io/resources/guides/build-ai-ready-design-system/)

[Product design glossary](https://designproject.io/resources/glossary/)

Canonical: https://designproject.io/resources/guides/create-semantic-color-tokens/

## Keep learning

- [How to create a UI color palette, step by step](https://designproject.io/resources/guides/create-ui-color-palette/)
- [Product design glossary: 44 terms explained](https://designproject.io/resources/glossary/)
- [How to check color contrast in a UI](https://designproject.io/resources/guides/check-color-contrast/)
