v1.6.x-dev NDS IQ v6

Tokens - National Design System

Every CSS custom property behind NDS: the color palette, primitive scales, semantic meanings, and component dials you can reference and override to retheme the system without forking its SCSS.

Token Hierarchy

NDS tokens are layered in four tiers. Always reference the highest tier that carries the meaning you need: a component token before a semantic one, a semantic token before a raw value. Higher tiers resolve down to lower ones, so a single re-theme or dark-mode switch cascades everywhere. To retheme the whole system from a few seed colors, see Themes.

Color Palette

The raw --colors-* values: brand slots, neutrals, status hues, and alpha ramps. Names carry no meaning, only shade. Never edited directly: re-theming regenerates the brand slots from seed colors.

Primitive Scales

The dimension vocabulary: named spacing, radius, and typography sizes such as --spacing-xl and --radius-md, each carrying its value directly. The size names are the entire scale.

Semantic Tokens

One name per meaning, system-wide: backgrounds, text, borders, icons, form-control surfaces, and shadows. Dark mode and re-themes re-bind this layer, so everything that references it adapts automatically.

Component Tokens

Per-component dials named --{component}-{property}-{variant}-{state}. Override one to retune a single component everywhere without touching the shared meanings other components consume.

Naming Grammar

Token names are contracts: they state what a value is for, never what color or shade it happens to be. Every name follows one of two patterns, built from a fixed vocabulary. The one sanctioned number is the opacity step in alpha families (--colors-green-alpha-10 is a 10% wash), matching the palette's own alpha convention.

Patterns

TierPatternExample
Semantic--{property}-{role}-{modifier}-{state}--text-oncolor-primary, --background-error-light
Component--{component}-{property}-{variant}-{state}--button-background-primary-hovered

Vocabulary

TermMeaning
lightA tinted wash of the base meaning, for soft surfaces and borders
faintThe faintest tint, one step below light: faint < light < solid
strongA deep, emphasized variant of the base meaning
oncolorThe element sits on a colored or dark fill, so the value stays legible there in every mode. Always placed last before the state
default / hovered / pressed / selected / focused / disabled / checkedThe state axis. A name without a state suffix is the resting state; pressed corresponds to :active

Primitive tokens

The raw value layer: the DGA color palette and the dimension scales. Each named size (--spacing-xl, --radius-md) carries its value directly; the size names are the whole scale.

:root { --spacing-none: 0px; --spacing-xxs: 2px; --spacing-xs: 4px; --spacing-sm: 6px; --spacing-md: 8px; --spacing-lg: 12px; --spacing-xl: 16px; --spacing-2xl: 20px; --spacing-3xl: 24px; --spacing-4xl: 32px; --spacing-5xl: 40px; --spacing-6xl: 48px; --spacing-7xl: 64px; --spacing-8xl: 80px; --spacing-9xl: 96px; --spacing-10xl: 128px; --spacing-11xl: 160px; }

Semantic tokens

The meaning layer. Each token indirects through a primitive, so overriding the palette or switching to dark mode re-resolves every consumer with no component changes.

:root { --background-default: var(--colors-base-white); --background-body: var(--colors-neutral-50); --background-nav: var(--colors-base-white); --background-topbar: var(--colors-neutral-100); --background-footer: var(--background-primary-strong); --background-hero: var(--background-primary-strong); --background-stripe: var(--colors-neutral-50); --background-sub-hero: var(--background-primary-light); --background-menu: var(--colors-base-white); --background-card: var(--colors-base-white); --background-card-hovered: var(--colors-neutral-50); --background-surface-elevated: var(--colors-base-white); --background-surface-sunken: var(--colors-neutral-200); --background-surface-oncolor: var(--colors-base-white); --background-overlay: var(--colors-alpha-black-50); --background-mark: var(--colors-primary-100); --background-primary-light: var(--colors-primary-50); --background-primary-faint: var(--colors-primary-25); --background-primary-strong: var(--colors-primary-900); --background-neutral-light: var(--colors-neutral-100); --background-primary: var(--colors-primary-600); --background-secondary: var(--colors-secondary-600); --background-tertiary: var(--colors-tertiary-600); --background-success: var(--colors-green-600); --background-success-light: var(--colors-green-50); --background-success-faint: var(--colors-green-25); --background-info: var(--colors-blue-600); --background-info-light: var(--colors-blue-50); --background-info-faint: var(--colors-blue-25); --background-warning: var(--colors-yellow-600); --background-warning-light: var(--colors-yellow-50); --background-warning-faint: var(--colors-yellow-25); --background-error: var(--colors-red-600); --background-error-light: var(--colors-red-50); --background-error-faint: var(--colors-red-25); --background-disabled: var(--colors-neutral-200); --background-disabled-primary: var(--colors-primary-200); --background-inverse-disabled: var(--colors-neutral-100); }

Component tokens

Per-component tokens, grouped by component. Each consumes a semantic or palette token; override one on a scope to restyle just that component.

:root { --autocomplete-highlight: var(--colors-primary-100); }

Knobs vs Tokens

NDS has two override surfaces with different jobs. Tokens theme the system: defined at :root, changing one retunes every matching component. Knobs style one element: undefined by default, set inline or on a wrapper, scoped to whatever they cascade into.

TokensKnobs
JobTheme the system or a whole component typeStyle one instance or one region
DefaultDefined at :root by NDSUndefined; the component falls back internally
Where you set it:root or a theme scopeThe element's style attribute or a wrapper class
Examples--button-background-primary-default, --background-card--btn-size, --card-width, --section-padding-block
/* Knob: this one button renders at 48px */ <button class="nds-btn nds-primary" style="--btn-size: 48px">Save</button> /* Token: every primary button in the app changes fill */ :root { --button-background-primary-default: var(--colors-tertiary-600); }

Each component resolves its knobs through a private variable with a fallback, so an unset knob simply uses the component's default. Every component page lists its knobs in the CSS Custom Properties table of its Usage Guidelines.

Dark Mode Behavior

Dark mode activates with <html data-theme="dark"> (token-matched, so it composes with theme names like "dark crimson"). It changes no component CSS: it only re-binds tokens.

  • The semantic layer flips first: page surfaces, text, borders, icons, and shadows take dark values, and everything consuming them follows
  • Component dials re-bind where a component needs its own dark correction: checkbox and switch fills darken for white-glyph contrast while radio brightens for its dot-with-border shape
  • The oncolor family deliberately does not flip: elements on brand fills keep their contrast in both modes
  • The palette never changes: --colors-neutral-100 is the same hex in both modes, which is why components reference meanings, not shades

Overriding with dark in mind

/* One value for both modes: override the token once */ :root { --background-card: #fffdf5; } /* A dark-specific value: re-bind it inside the dark scope */ :root[data-theme~="dark"] { --background-card: #26221a; }

Mode switching, persistence, and the seed-color theme engine are covered on the Themes page.

Usage Guidelines

Best Practices

  • Reference tokens with var(--token-name). Never hardcode a hex color, pixel value, or shadow that a token already names
  • Prefer the highest tier that fits: a component token over a semantic token over a raw primitive. The higher the tier, the more a re-theme or dark mode does for you for free
  • Reach for a primitive directly only when no semantic token carries the meaning (for example a one-off spacing or radius value)
  • To restyle a single component, override its --{component}-* token on a scope rather than editing the component's SCSS: .my-scope { --button-background-primary-default: var(--colors-tertiary-600); }
  • To retheme the whole system, override the brand slots (--colors-primary-* and friends). Semantic and component tokens re-resolve automatically. The Themes page generates a full palette from a few seed colors
  • Do not hardcode light-mode values. Dark mode re-binds the semantic layer, so a component that references --text-default flips correctly while one pinned to --colors-base-black will not
  • Always pair a typography size token with its line-height: --typo-text-md-FS with --typo-text-md-LH
  • Match spacing by value when reading the scale: --spacing-xl is 16px, --spacing-md is 8px
  • Use knobs (--btn-size, --card-width) for one-off instance styling and tokens for theme-wide changes; a knob set on a wrapper styles everything inside it
  • When you override a component token globally, give it a dark value too by re-binding it inside :root[data-theme~="dark"]

How Tokens Resolve

Every painted value walks a chain from a knob or component dial down to the palette. Two real chains from the source:

/* The .nds-primary button fill: knob, component dial, palette */ --btn-bg: var(--button-background-primary-default); /* knob, set by the variant class */ --button-background-primary-default: var(--colors-primary-600); /* component tier */ --colors-primary-600: #1b8354; /* palette */ /* The checked checkbox fill: dial, shared semantic surface, palette */ --checkbox-primary-checked: var(--controls-primary-checked); /* component dial */ --controls-primary-checked: var(--colors-primary-600); /* semantic */

Override any link in the chain and everything above it follows. This reference is generated directly from the token SCSS at build time, so the names and values on this page always match what ships.

Last Modified Date: 25/07/2026 - 08:05 PM
Was this page useful?
60% of users said Yes from 2843 Feedbacks