Code Quality Help

Color Theming & Design Tokens Standard

Field

Value

Document type

Engineering Standard

Applies to

Angular applications (frontend)

Status

Active

Token format

W3C Design Tokens Community Group (DTCG), Stable — 2025-10

Build tool

Style Dictionary v4

Owner

Frontend Team

1. Purpose

This document defines the mandatory approach for implementing color theming (light/dark and any future themes) in Angular applications. It replaces ad-hoc SCSS variable lists with a token-driven architecture that:

  • separates design intent (what a color means, e.g. "danger") from design values (what a color is, e.g. #ea0079);

  • allows runtime theme switching without recompiling application CSS;

  • gives designers a structured, visual way to change colors without touching source code;

  • catches naming/reference mistakes at build time, not in production.

Compliance with this standard is mandatory for all new frontend modules and recommended for legacy modules undergoing refactor.

Keywords MUST, MUST NOT, SHOULD, SHOULD NOT, MAY are used per RFC 2119.

2. Scope

This standard covers:

  • the token architecture (reference / semantic / component layers);

  • the source-of-truth file format (DTCG JSON);

  • the build pipeline that generates CSS, SCSS, TypeScript and documentation artifacts from token sources;

  • the runtime mechanism for theme switching in the browser;

  • the collaboration workflow between designers (Figma) and engineers (Git-based code review);

  • governance rules enforced via linting and CI.

This standard does not cover typography, spacing, or motion tokens, although the same architecture SHOULD be reused for those domains.

3. Definitions

Term

Definition

Design Token

A named, single source of truth for a design decision (e.g. a color), stored as data rather than hard-coded in a stylesheet.

Reference Token

The raw palette value (e.g. clr.secondary.1 = #ffffff). Not used directly by components.

Semantic Token

A token named after its purpose (e.g. surface, text-primary), aliasing one Reference Token per theme. This is the layer components MUST consume.

Component Token

An optional, narrowly-scoped token for a single component's exception (e.g. btn-primary-bg), used only when semantic tokens are insufficient.

DTCG

Design Tokens Community Group format — the W3C-track JSON standard for representing design tokens ($value, $type, $description).

Style Dictionary

The build tool that transforms token JSON into platform-specific outputs (CSS, SCSS, TS, Markdown).

Theme

A named set of Reference Token values (e.g. light, dark) activated by a CSS class on <body>.

4. Architecture Overview

Tokens are organized in three layers. Components MUST consume the Semantic layer only; the Reference layer MUST NOT be referenced directly from component stylesheets (enforced by lint rule, see §13).

Design Tokens (source of truth)Generated ArtifactsReference Tokens clr.secondary.1..10clr.accent.1..10clr.warn.1..10Semantic Tokens surface, surface-varianttext-primary, text-secondaryaction-primary, dangerComponent Tokens (optional) btn-primary-bgcard-elevated-shadowCSS Custom Properties body.theme_light { --clr-*: #hex }body.theme_dark { --clr-*: #hex }SCSS Variables $surface: var(--clr-secondary-1);TypeScript Object colorTokens.surfaceAngular ComponentsMUST NOT be importeddirectly by componentsMUST be the only layercomponents importStyle Dictionary(per-theme build)alias reference{clr.secondary.1}Style Dictionary(custom format)Style Dictionary(custom format)optional overrideconsumed ascolors.$surfaceconsumed in.ts (canvas/charts)resolved at runtimeby the browser

4.1 Reference Tokens

  • Raw palette values only (clr.secondary.1clr.secondary.10, etc.).

  • MUST have identical key structure across all themes (tokens/light/*.json, tokens/dark/*.json) — the same key MUST exist in every theme file with a theme-appropriate value.

  • MUST NOT carry semantic meaning in the name (no clr.danger, only clr.warn.6).

4.2 Semantic Tokens

  • Named after purpose, not appearance (surface, not white).

  • MUST alias a Reference Token via {group.subgroup.index} syntax.

  • SHOULD carry a $description explaining intended usage.

  • Is the only layer application code is allowed to consume.

4.3 Component Tokens (optional)

  • Used only when a single component needs a value that does not fit any existing Semantic Token and reuse elsewhere is not expected.

  • MUST still alias a Semantic Token (never a Reference Token directly).

  • SHOULD be kept local to the component's SCSS file, not in the shared token pipeline, unless reused by 2+ components.

5. Repository Structure

design-tokens/tokens/light/semantic/tools/src/styles/tokens/ (generated)app/shared/ (generated)docs/ (generated)style-dictionary.config.mjsclr.jsondark/semantic.jsonmigrate-to-dtcg.mjs_reference-light.css_reference-dark.css_semantic.scsscolor-tokens.tscolor-tokens.mddocs/npm run tokens:buildnpm run tokens:build

5.1 Rules

  • tokens/**/*.json files MUST be treated as the only source of truth for color values. Generated files (_reference-*.css, _semantic.scss, color-tokens.ts, color-tokens.md) MUST carry an AUTOGENERATED header comment and MUST NOT be hand-edited.

  • style-dictionary.config.mjs MUST resolve all file paths relative to its own location (import.meta.url), never to process.cwd(), so that the build behaves identically regardless of the invoking directory.

6. Token File Format (DTCG)

All token source files MUST use the W3C DTCG format (stable specification, 2025-10). Legacy Style Dictionary format (value/comment) MUST NOT be used in new files.

tokens/light/clr.json — Reference layer, one file per theme:

{ "clr": { "secondary": { "1": { "$value": "#ffffff", "$type": "color" }, "10": { "$value": "#3c3c3c", "$type": "color" } }, "accent": { "6": { "$value": "#1ab010", "$type": "color" } }, "warn": { "6": { "$value": "#ea0079", "$type": "color" } } } }

tokens/semantic/semantic.json — Semantic layer, alias-only:

{ "semantic": { "surface": { "$value": "{clr.secondary.1}", "$type": "color", "$description": "Card and page background" }, "text-primary": { "$value": "{clr.secondary.10}", "$type": "color", "$description": "Primary text color" }, "danger": { "$value": "{clr.warn.6}", "$type": "color", "$description": "Errors, destructive actions" } } }

6.1 Rules

  • Every semantic token's $value MUST be an alias in the form {group.subgroup.index} — literal hex values in the semantic layer are FORBIDDEN and MUST fail the build (see §8.1).

  • $type: "color" MUST be set for every color token to enable type-aware tooling (Tokens Studio, contrast checkers, IDE plugins).

  • $description SHOULD be present for every semantic token; it is surfaced in the generated documentation (§11) and in code comments.

7. Build Pipeline (Style Dictionary)

A single Node.js script (style-dictionary.config.mjs) drives four parallel build outputs from the same token sources.

tokens/light/*.json (DTCG)tokens/dark/*.json (DTCG)tokens/semantic/*.json (DTCG, aliases)Style Dictionary platform "css" (light source)body.theme_light {--clr-secondary-1: #ffffff;... Style Dictionary platform "css" (dark source)body.theme_dark {--clr-secondary-1: #3c3c3c;... Custom format"scss/semantic-css-vars"$surface: var(--clr-secondary-1)Custom format"ts/semantic-css-vars"export const colorTokens = { surface: 'var(--clr-secondary-1)'Resolve light + dark dictionariesvia getPlatformTokens()Custom format"markdown/color-tokens"Table with color swatches\nfor both themes-> color-tokens.mdnpm run prebuild (Angular CLI hook)ng build / ng serve_reference-light.css_reference-dark.css_semantic.scsscolor-tokens.ts

7.1 Alias validation (build-time safety)

The custom SCSS/TS formats MUST throw a build error if a semantic token's $value is not a valid {alias} reference:

const raw = token.original.$value; const match = /^\{(.+)\}$/.exec(raw); if (!match) { throw new Error( `Token "${token.path.join('.')}" must reference a Reference Token ` + `via {alias}, got: "${raw}"` ); }

A typo in an alias path (e.g. {clr.secondar.1}) MUST cause the build to fail with Reference Errors: Some token references could not be found — this is a hard requirement, not an optional lint warning.

7.2 npm scripts

{ "scripts": { "tokens:build": "node design-tokens/style-dictionary.config.mjs", "prebuild": "npm run tokens:build", "start": "npm run tokens:build && ng serve", "build": "npm run tokens:build && ng build" } }

prebuild MUST run before every ng build/ng serve invocation so that generated artifacts can never go stale relative to token sources.

8. Runtime Theme Switching

Theme switching happens entirely in the browser via CSS Custom Properties — no application rebuild or SCSS recompilation is required to change the active theme.

UserUserAngular ComponentAngular Component<body> element<body> elementBrowser CSS EngineBrowser CSS Engineclicks "Toggle theme"body.classList.toggle('theme_dark')selector "body.theme_dark" now matchesre-evaluate custom properties--clr-secondary-1: #3c3c3c (was #ffffff)every declaration usingvar(--clr-secondary-1) repaints,no JS/CSS recompilation needed$surface (SCSS) compiled once to"var(--clr-secondary-1)" — the SCSSlayer never needs rebuilding whenthe theme changes at runtime.

8.1 Rules

  • Theme selection MUST be expressed as a class on <body> (theme_light/theme_dark), not via [data-theme] attribute or inline styles, for consistency with the generated CSS selectors.

  • To avoid a flash of the wrong theme on load (FOUC), the persisted theme preference MUST be applied to <body> by an inline script in index.html, executed before Angular bootstraps.

  • New themes (e.g. theme_high_contrast) MUST reuse the same Semantic Token names; only a new tokens/<theme-name>/clr.json file and a corresponding Style Dictionary build target are required — no changes to component code.

9. Consuming Tokens in Angular Components

9.1 SCSS (preferred for styling)

@use "styles/tokens/semantic" as colors; .card { background: colors.$surface; color: colors.$text-primary; border: 1px solid colors.$border-default; } .btn-primary { background: colors.$action-primary; &:hover { background: colors.$action-primary-hover; } }

9.2 TypeScript (for Canvas, Charts, inline styles)

import { colorTokens } from '@app/shared/color-tokens'; this.chartOptions.color = colorTokens.actionPrimary; // 'var(--clr-accent-6)'

9.3 Rules

  • Components MUST import tokens via @use "styles/tokens/semantic".

  • Components MUST NOT reference var(--clr-*) (Reference layer) directly in component stylesheets. This is enforced by a stylelint rule (§13).

  • Components MUST NOT hard-code hex colors in .scss/.ts files outside of the token source (tokens/**/*.json).

10. Generated Documentation

Every npm run tokens:build regenerates docs/color-tokens.md — a human-readable table of all Semantic Tokens with resolved color swatches for every theme, side by side, plus the intended usage description.

SCSS variable

CSS custom property

Light

Dark

Purpose

$surface

var(--clr-secondary-1)

#ffffff

#3c3c3c

Card and page background

$danger

var(--clr-warn-6)

#ea0079

#ea348f

Errors, destructive actions

This file MUST be treated as read-only documentation (regenerated, not edited) and SHOULD be linked from the project README and any Storybook "Colors" page.

11. Designer Collaboration Workflow

Designers MUST NOT edit token JSON files directly. All design-side changes flow through Figma + Tokens Studio plugin, which provides a visual UI (color pickers, theme switches) over the same DTCG token structure and handles serialization back to JSON.

DesignerDesignerFigma +Tokens Studio pluginFigma +Tokens Studio pluginGit RepositoryGit RepositoryCI PipelineCI PipelineDeveloperDeveloperedits token via UI(color picker, theme tabs)no raw JSON is shown"Push to GitHub"(plugin serializes state to DTCG JSON)opens Pull Requestnpm run tokens:build(alias validation, fails on typo)WCAG contrast checkalt[validation fails]PR marked failing,reason shown in CI log[validation passes]PR green,updated color-tokens.md attached as diffcode review of JSON diff(hex value changes only)merge PRprebuild hook regeneratesCSS / SCSS / TS / MD

11.1 Rules

  • The Figma project MUST be configured to use the W3C DTCG token format (Tokens Studio settings → Token Format), matching the repository's format exactly.

  • Multi-file sync (separate light.json/dark.json/semantic.json files matching the repository layout) requires a Tokens Studio Pro license (Multi-file Sync to Remote Storage). Teams without a Pro license MUST use Token Sets within a single tokens.json and add a pre-build step that splits it into the repository's file layout.

  • All token changes from design MUST arrive as a Pull Request and MUST go through standard code review before merge — no direct pushes to the default branch.

12. Governance & Enforcement

12.1 Stylelint rule (mandatory)

The Reference layer (var(--clr-*)) MUST NOT be used directly in component stylesheets. This is enforced automatically:

// .stylelintrc rules: { "declaration-property-value-disallowed-list": [ { "/color|background|border-color/": ["/var\\(--clr-/"] } ] }

An overrides exemption MUST be scoped only to files inside src/styles/tokens/ (the generated Reference/Semantic layer itself).

12.2 CI drift check (mandatory)

CI MUST fail if generated artifacts do not match what the current token sources would produce — this catches cases where a token source was edited but the build was not re-run before commit:

- name: Verify generated tokens are in sync run: | npm run tokens:build git diff --exit-code src/styles/tokens/ src/app/shared/color-tokens.ts docs/color-tokens.md

A WCAG contrast check SHOULD run in CI for critical semantic pairs (e.g. text-primary on surface) to prevent accessibility regressions introduced by a palette change:

test('text-primary on surface meets WCAG AA', () => { const ratio = getContrastRatio(resolve('secondary.10'), resolve('secondary.1')); expect(ratio).toBeGreaterThanOrEqual(4.5); });

13. Versioning & Change Management

  • Breaking changes to Semantic Token names (rename/removal) MUST be communicated to all consuming teams before merge, since component code references these names directly.

  • Reference Token value changes (palette tuning) are non-breaking for component code (aliases stay valid) but MAY change the visual contrast of the affected theme — the CI contrast check (§12.3) is the safety net for this case.

  • Adding a new theme (e.g. theme_high_contrast) is additive and MUST NOT require changes to Semantic Token names or component code — only a new tokens/<theme>/clr.json and a new Style Dictionary build target.

14. Appendix A — Migrating Legacy (non-DTCG) Token Files

If existing token files use the legacy Style Dictionary format (value/comment), they MUST be migrated to DTCG before being adopted under this standard. Use the provided migration script (design-tokens/tools/migrate-to-dtcg.mjs), then:

  1. add usesDtcg: true to every StyleDictionary instance in style-dictionary.config.mjs;

  2. update custom formats to read token.original.$value (raw alias) and token.$value (resolved value) instead of the unprefixed value/comment fields;

  3. re-run npm run tokens:build and diff the generated artifacts against the pre-migration output — the diff SHOULD be empty aside from JSON key ordering.

15. Appendix B — Glossary

Term

Meaning

Reference Token

Raw palette value, internal implementation detail.

Semantic Token

Purpose-named token; the public API for component styling.

Component Token

Narrow, component-scoped exception aliasing a Semantic Token.

Alias

A {group.path} reference from one token to another.

DTCG

W3C Design Tokens Community Group JSON format.

FOUC

Flash Of Unstyled/wrong-themed Content on initial page load.

10 August 2026