Colour tokens have one semantic name across design and code. Start by loading the beta token bundle, then bind components to the names documented here.
Load the CSS tokens
Install the beta package:
pnpm add @equinor/eds-tokens@beta
Import the bundled variables once in your application:
@import '@equinor/eds-tokens/next/css/variables.css';
The bundle includes the semantic colours for both light and dark. Components then use CSS custom properties:
.button--primary {
background: var(--eds-background-interactive-accent-emphasis-default);
color: var(--eds-text-on-emphasis-accent);
}
next path is temporaryThe 3.0.0 beta publishes the new output under next/* while legacy and new components coexist. The
prefix will be removed before the stable release. The token names remain the same.
Reading a token name
A name answers five questions, from left to right:
| Surface | Behaviour | Tone | Level | State |
|---|---|---|---|---|
| background | interactive | accent | emphasis | hover |
| Segment | Question | Example |
|---|---|---|
| Surface | What am I colouring? | background, border, text, icon |
| Behaviour | Can the user act on it? | interactive, non-interactive |
| Tone | What does it mean? | accent, neutral, danger |
| Level | How visually strong is it? | muted, emphasis, and either default or selected, depending on surface and behaviour |
| State | What is happening now? | default, hover, pressed |
Not every name contains all five segments. Non-interactive colours have no state, and families such
as text.primary and background.surface name a specific role directly.
Usage explains how to choose the branch, tone, level and state.
One name in Figma, CSS and TypeScript
Each colour token has one name in Figma, one CSS custom property and one TypeScript path:
| Where | How it is written |
|---|---|
| Figma | background.non-interactive.accent.muted |
| CSS | --eds-background-non-interactive-accent-muted |
| TypeScript | semantic.background.nonInteractive.accent.muted |
CSS is --eds- followed by the dotted name with hyphens for dots. TypeScript uses the dotted name
with each segment camel-cased.
The token reference is the authority for CSS names. The CSS name and Figma name are carried by the token definition rather than derived from each other, so if the pattern and reference ever disagree, the reference is right.
Look up the token instead of copying a hex value. A hardcoded value will not follow the colour scheme, and nothing will tell you when it stops matching the token it came from.
Switching colour scheme
The system reads the data-color-scheme attribute. Light is the default, so a page without the
attribute is light:
<!-- Light, the default -->
<html data-color-scheme="light"></html>
<!-- Dark -->
<html data-color-scheme="dark"></html>
Set it once, at the top of the page or application, so the whole interface switches together.
The attribute can be set on any ancestor and it cascades, so scoping a single panel to dark inside a light page is technically possible. We do not recommend it. A colour scheme describes the interface, not a region inside it.
Using the tokens in TypeScript
The tokens also ship as TypeScript objects for places a CSS custom property cannot reach, such as a chart library, canvas, email template or React Native surface.
import { semantic } from '@equinor/eds-tokens/next/ts/semantic/light'
semantic.background.interactive.accent.emphasis.default // '#21767e'
Each module is imported directly; there is no barrel. The objects are as const, and each one also
exports its type, so Semantic gives you autocompletion and a compile error on a name that does not
exist.
In CSS you write one name and the data-color-scheme attribute decides the value. In TypeScript
there is no attribute to read: semantic/light and semantic/dark are separate objects, and you
choose which one to use.
import { semantic as light } from '@equinor/eds-tokens/next/ts/semantic/light'
import { semantic as dark } from '@equinor/eds-tokens/next/ts/semantic/dark'
const tokens = prefersDark ? dark : light
Prefer CSS custom properties wherever they work. Reach for the TypeScript objects only where they do not, and expect to handle the scheme yourself.
Values are hex rather than the OKLCH used by the CSS output. They are generated from the same source, so they agree, but a value copied from TypeScript does not carry the CSS output's wide-gamut precision.
From next/ts/, import semantic. The other modules (colorScheme, colors, font, elevation
and primitives) hold the layers that semantic is resolved from. They are internal and can be
renamed or restructured in any release. The palette explains the colour layers as
concepts.
density is a temporary exception. semantic carries the Comfortable values for spacing, corner
radius and typography, so an app in another density imports those groups from density/compact or
density/relaxed instead. The exception lasts until the TypeScript output ships one file per colour
scheme and density.
For designers
Add the EDS colour library to your Figma file and choose variables by semantic name. The names are identical to the ones above, so a design and its implementation refer to the same role.
Only the semantic layer is published to the variable picker. The numbered scale underneath it is hidden, so a raw palette value cannot be bound to a component by accident.
How variable scopes filter the picker
Each variable has scopes that decide which pickers in Figma offer it. The scopes are a general guideline that steers each picker towards the property a token is usually used on. Background tokens are offered for the fill of a frame or shape, border tokens for strokes, and text tokens for the fill of a text layer, so the list you open is already narrowed to the usual choices.
The scopes do not define which tokens are allowed on which property. Some tokens are offered in more than one picker on purpose:
background.surfaceis also offered for strokes, because some places in Figma need a stroke in the surface colour.- The spacing variables are also offered for width and height, because icons are sized with them.
Button binds the width and height of its icon to
spacing.lg.
If a token is missing from a picker, it is usually meant for a different property.
Next step
Usage shows how to choose levels and states, pair foregrounds and apply the tokens to common interface patterns.