Design tokens used in the EDS, including colors, spacing, typography, and more. These tokens are synchronized with Figma variables and provide a single source of truth for design decisions across the design system.
For LLM instructions on using colors, see the instructions/colors.md, or jump directly to the static or dynamic instructions.
pnpm add @equinor/eds-tokens
⚠️ Transition notice. The token structure in this package is being replaced. The replacement is developed on the beta line (@equinor/eds-tokens@beta) and ships as the next major release.
- Need tokens today? Use the CSS variables — they are the supported option for production and keep working throughout 2.x. Expect the variable names to change in the next major release; keeping direct variable references few and centralised will make that migration cheap.
- Do not adopt the legacy
tokensJavaScript object in new code — it is deprecated and will be removed in the next major release.- The beta line is for testing and feedback, not production use.
The package provides two token systems:
- CSS Variables -- Theme-aware design tokens synced from Figma. Supported for production; the variable names will change in the next major release.
- Legacy Tokens -- Original token format. Deprecated: do not use in new code.
These
--eds-*variables will be replaced by the new token structure in the next major release. They remain the supported way to consume EDS tokens today (@equinor/eds-core-react2.x requirescss/variables) — just keep direct variable references organised so the rename in the next major is easy to absorb.
The new token system uses CSS custom properties that automatically adapt to light and dark color schemes via explicit [data-color-scheme] scope rules with a prefers-color-scheme media-query fallback. These tokens are directly synced from Figma variables.
Dual-mode color tokens are emitted as explicit scope rules in the published variables.min.css:
:root { --eds-color-bg-floating: #fff; }
[data-color-scheme="light"] { --eds-color-bg-floating: #fff; }
[data-color-scheme="dark"] { --eds-color-bg-floating: #202223; }
@media (prefers-color-scheme: dark) {
:root:not([data-color-scheme="light"]) {
--eds-color-bg-floating: #202223;
}
}The published CSS does not use the light-dark() CSS function. This is deliberate: light-dark() is correct in source CSS, but downstream bundlers (Vite 8 + Rolldown + lightningcss, esbuild with legacy targets, postcss with certain presets) may polyfill it into a var() pattern that does not work for subtree-scoped dark mode. Emitting explicit scope rules is robust against any downstream CSS pipeline regardless of how it is configured.
To enable dark mode for a subtree, set data-color-scheme="dark" on a wrapper element. To opt out of system dark mode for a subtree, set data-color-scheme="light".
Import the variables stylesheet:
@import '@equinor/eds-tokens/css/variables';Then use the CSS custom properties in your styles:
.my-component {
background-color: var(--eds-color-bg-neutral-surface);
color: var(--eds-color-text-neutral-strong);
border: 1px solid var(--eds-color-border-neutral-subtle);
padding: var(--eds-selectable-space-vertical) var(--eds-selectable-space-horizontal);
border-radius: var(--eds-spacing-border-radius-rounded);
}The variables automatically respond to color scheme changes:
/* Applies to elements with data-color-scheme attribute */
[data-color-scheme="dark"] {
/* Variables automatically use dark mode values */
}
/* Or use the native prefers-color-scheme */
@media (prefers-color-scheme: dark) {
/* Variables automatically use dark mode values */
}For scenarios where you need colour variables in JavaScript:
Import the light and dark semantic color tokens:
// Import semantic color scheme tokens
import * as lightSemantic from '@equinor/eds-tokens/js/color/color-scheme/light-semantic'
import * as darkSemantic from '@equinor/eds-tokens/js/color/color-scheme/dark-semantic'
// Use semantic tokens with light/dark values
const lightSurface = lightSemantic.BG_NEUTRAL_SURFACE
const darkSurface = darkSemantic.BG_NEUTRAL_SURFACE
const lightAccent = lightSemantic.BG_ACCENT_FILL_EMPHASIS_DEFAULT
const darkAccent = darkSemantic.BG_ACCENT_FILL_EMPHASIS_DEFAULT
const lightBorder = lightSemantic.BORDER_INFO_MEDIUM
const darkBorder = darkSemantic.BORDER_INFO_MEDIUM// Import spacing values for different density modes
import * as comfortableSpacing from '@equinor/eds-tokens/js/spacing/comfortable'
import * as spaciousSpacing from '@equinor/eds-tokens/js/spacing/spacious'
// Use the values (numbers in pixels)
const padding = comfortableSpacing.SPACING_INLINE_MD
const borderRadius = comfortableSpacing.SPACING_BORDER_RADIUS_ROUNDEDTypography is composed at runtime from five orthogonal axes. The CSS bundle resolves them via data-* attributes; non-CSS consumers (React Native, SSR, design tooling) import the family-keyed matrix directly:
import { typography as ui } from '@equinor/eds-tokens/ts/typography/font-family-ui'
import { typography as header } from '@equinor/eds-tokens/ts/typography/font-family-header'
const md = ui.fontFamilySize.md
const style = {
fontFamily: ui.typography.fontFamily,
fontSize: md.fontSize,
fontWeight: md.fontWeight.normal,
lineHeight: md.lineHeight.default,
letterSpacing: md.tracking.normal,
}
// { fontFamily: 'Inter', fontSize: 14, fontWeight: 400,
// lineHeight: 20, letterSpacing: 0 }Each size cell exposes fontSize, nested fontWeight / tracking / lineHeight objects, and inlined iconSize / gapHorizontal / gapVertical extras for chip- and button-like layouts. Variant names can be derived with keyof:
type Weight = keyof typeof ui.fontFamilySize.md.fontWeight // 'lighter' | 'normal' | 'bolder'For React Native, coerce fontWeight to a string at the call site (String(md.fontWeight.normal)) so it slots into <Text style>.
See instructions/typography.md for the full axis model.
The variables are available in two formats:
-
Flat format (
/flat/) -- Simple key-value pairs for easy access -
Nested format (
/nested/) -- Hierarchical structure matching the token naming -
The semantic color tokens are available for light and dark color schemes.
-
light-semantic.json/dark-semantic.json-- Semantic color tokens for each color scheme
// Import flat format tokens
import lightSemanticFlat from '@equinor/eds-tokens/json/color/color-scheme/flat/light-semantic.json'
import darkSemanticFlat from '@equinor/eds-tokens/json/color/color-scheme/flat/dark-semantic.json'
// Access values directly
const lightSurface = lightSemanticFlat['bg-neutral-surface'] // "#ffffff"
const darkSurface = darkSemanticFlat['bg-neutral-surface'] // "#262626"// Import nested format tokens
import lightSemanticNested from '@equinor/eds-tokens/json/color/color-scheme/nested/light-semantic.json'
import darkSemanticNested from '@equinor/eds-tokens/json/color/color-scheme/nested/dark-semantic.json'
// Access values via hierarchical structure
const lightSurface = lightSemanticNested.bg.neutral.surface // "#ffffff"
const darkSurface = darkSemanticNested.bg.neutral.surface // "#262626"- Colors -- Semantic color tokens for backgrounds, text, borders, and states
- Spacing -- Layout spacing including inline, stack, inset, and border radius
- Typography -- Font sizes, line heights, and font families (requires font files, see below)
The typography system requires two font families: Equinor (headings) and Inter (UI/body text). Load both via the EDS variable font stylesheet:
<link rel="stylesheet" href="https://cdn.eds.equinor.com/font/eds-uprights-vf.css" />Note: The older
equinor-font.cssonly includes the Equinor font. Components usingdata-font-family="ui"(Button, TextField, Input, etc.) will fall back to a generic sans-serif if Inter is not loaded.
- Font family setup (UI and Header fonts)
- Font size data attributes (
[data-font-size='xs'],[data-font-size='sm'], etc.) - Line height data attributes (
[data-line-height='default'],[data-line-height='squished']) - Font weight data attributes (
[data-font-weight='lighter'],[data-font-weight='normal'],[data-font-weight='bolder']) - Letter spacing data attributes (
[data-tracking='tight'],[data-tracking='normal'],[data-tracking='wide']) - Baseline grid alignment (
[data-baseline='grid'],[data-baseline='center'])
The data-font-family sets these CSS custom properties:
[data-font-family] {
font-size: var(--eds-typography-font-size);
font-family: var(--eds-typography-font-family);
font-weight: var(--eds-typography-font-weight);
line-height: var(--eds-typography-line-height);
letter-spacing: var(--eds-typography-tracking);
}Using Data Attributes
Set typography properties using data attributes:
<!-- UI font with medium size -->
<p data-font-family="ui" data-font-size="md" data-line-height="default">
UI font text
</p>
<!-- Header font with extra large size and bolder weight -->
<h1 data-font-family="header" data-font-size="xl" data-font-weight="bolder">
Header font text
</h1>
<!-- Baseline grid alignment -->
<p data-font-family="ui" data-font-size="md" data-baseline="grid">
Aligned to 4px baseline grid
</p>The spacing system supports different density modes:
spacious-- Default density for most applicationscomfortable-- All the spacing values goes down one level on the scale in a way to maximize the space.
Selectable Spacing (for buttons, cards, and interactive elements)
Adapt to data-selectable-space (xs/sm/md/lg/xl) and data-space-proportions (squished/squared/stretched):
.button {
padding-inline: var(--eds-selectable-space-horizontal);
padding-block: var(--eds-selectable-space-vertical);
gap: var(--eds-selectable-gap-vertical) var(--eds-selectable-gap-horizontal);
} <button class="button" data-selectable-space="md" data-space-proportions="squished">
Button
</button>Container Spacing (for container elements)
Spacing values that adapt to data-space-proportions:
.container {
padding-inline: var(--eds-container-space-horizontal);
padding-block: var(--eds-container-space-vertical);
gap: var(--eds-container-gap-vertical) var(--eds-container-gap-horizontal);
}Page Spacing (for page-level elements)
Spacing values that adapt to data-space-proportions:
.page {
padding-inline: var(--eds-page-space-horizontal);
padding-block: var(--eds-page-space-vertical);
gap: var(--eds-page-gap-vertical) var(--eds-page-gap-horizontal);
}The foundation CSS includes baseline grid alignment for consistent vertical rhythm using data attributes:
data-baseline="grid"-- Aligns text baseline to 4px grid intervalsdata-baseline="center"-- Centers text vertically while maintaining 4px grid alignment
<p data-font-family="ui" data-font-size="md" data-baseline="grid">
Text aligned to baseline grid
</p>These utilities use modern CSS features (text-box-trim) and gracefully degrade in older browsers.
Deprecated. The original token format remains published only so existing applications keep working, and will be removed in the next major release. These tokens use a structured JavaScript object format.
import { tokens } from '@equinor/eds-tokens'
// Access token values
const primaryColor = tokens.colors.interactive.primary__resting.rgba
const spacing = tokens.spacings.comfortable.medium
const typography = tokens.typography.heading.h1- Clickbounds
- Colors
- Elevation
- Shape
- Spacing
- Interaction states
- Typography (
ot,wofforwoff2font required)
Do not adopt these legacy tokens in new projects. If you need tokens today, use the CSS variables above; the long-term replacement is the new token structure currently on the beta line (
@equinor/eds-tokens@beta).
This repository contains a couple of GitHub Actions workflows:
- Sync tokens to Figma
- Sync Figma variables to tokens
These workflows demonstrate bi-directional syncing between variables in Figma and design tokens in a codebase using Figma's Variables REST API. For more background and a graphical representation of what these workflows do, see our Syncing design systems using Variables REST API FigJam file.
To use these workflows, you should copy the code in this repository into your organization and modify it to suit the needs of your design processes.
To use the "Sync Figma variables to tokens" workflow, you must be a full member of an Enterprise org in Figma. To use the "Sync tokens to Figma" workflow, you must also have an editor seat.
Both workflows assume that you have a single Figma file with local variable collections, along with one or more tokens json files in the tokens/ directory that adhere* to the draft W3C spec for Design Tokens. For demonstration purposes, this directory contains the tokens exported from the Get started with variables Community file. Have a copy of this file ready if you want to try out the workflow with these existing tokens.
*See
src/token_types.tsfor more details on the format of the expected tokens json files, including the deviations from the draft design tokens spec we've had to make. We expect there to be one tokens file per variable collection and mode.
In addition, you must also have a personal access token for the Figma API to allow these workflows to authenticate with the API. For the "Sync Figma variables to tokens" workflow, the token must have at least the Read-only Variables scope selected. For the "Sync tokens to Figma" workflow, the token must have the Read and write Variables scope selected.
Before running either of these workflows, you'll need to create an encrypted secret in your repository named GH_ACTION_VARIABLES_SYNC_FIGMA_TOKEN containing your personal access token.
Both workflows are configured to run manually for demonstration purposes, and are designed to be as conservative as possible in their functionality (see details below).
To run the "Sync Figma variables to tokens" workflow:
- Open the workflow under the Actions tab in your repository and click Run workflow
- You will be asked to provide the file key of the Figma file. The file key can be obtained from any Figma file URL:
https://www.figma.com/file/{file_key}/{title}. - After the workflow finishes, you should see a new pull request if there are changes to be made to the tokens files in the
tokens/directory. If there are no changes to be made, then a pull request will not be created.
This workflow has some key behaviors to note:
- After generating the new tokens json files, this workflow creates a pull request for someone on the team to review. If you prefer, you can modify the workflow to commit directly to a designated branch without creating a pull request.
- If a variable collection or mode is removed from the Figma file, the corresponding tokens file will not be removed from the codebase.
To run the "Sync tokens to Figma" workflow:
- Open the workflow under the Actions tab in your repository and click Run workflow
- You will be asked to provide the file key of the Figma file. The file key can be obtained from any Figma file URL:
https://www.figma.com/file/{file_key}/{title}. Note: if you are trying out this workflow for the first time, use a file that is separate from your design system to avoid any unintended changes. - After the workflow finishes, open the file in Figma and observe that the variables should be updated to reflect the tokens in your tokens files.
This workflow has some key behaviors to note:
- Though this workflow is configured to run manually, you're free to modify it to run on code push to a specified branch once you are comfortable with its behavior.
- When syncing to a Figma file that does not have any variable collections, this workflow will add brand-new collections and variables. When syncing to a Figma file that has existing variable collections, this workflow will modify collections and variables in-place using name matching. That is, it will look for existing collections and variables by name, modify their properties and values if names match, and create new variables if names do not match.
- The workflow will not remove variables or variable collections that have been removed in your tokens files.
- When mapping aliases to existing local variables, we assume that variable names are unique across all collections in the Figma file. Figma allows duplicate variable names across collections, so you should make sure that aliases don't have naming conflicts in your file.
- For optional Figma variable properties like scopes and code syntax, the workflow will not modify these properties in Figma if the tokens json files do not contain those properties.
- If a string variable is bound to a text node content in the same file, and the text node uses a shared font in the organization, that variable cannot be updated and will result in a 400 response.
You can run the GitHub actions locally by running pnpm install and creating a .env file.
Example:
PERSONAL_ACCESS_TOKEN="your_personal_access_token"
How to create your personal access token
and then running:
# Defaults to writing to the tokens directory
pnpm run update-tokens
# and / or
pnpm run update-figma- The semantic variable is the variable applied to a layer in Figma.
- A semantic variable can have different modes defined, and the designer can select these
The semantic variable references the first segment (collection) in the variable. For example, the first segment is “appearance” for the action variables. Variables defined in appearance point to the next segment, which for action variables would be prominence. In the prominence collection, we define a variable for each of the appearance modes so that these are represented in the context of each prominence mode. The variables in the “prominence” collection point to variables in the state collection, and again, we represent all the prominence modes as new variables in the state collection. For action variables, the journey ends here, and the variables in the state collection point to the light/dark color scheme variables in the color scheme collection.
The color scheme collection variables support all the combinations of modes in the different collections and are used to generate tokens in code. All the combinations of modes in different collections must be provided here so that the code syntax matches tokens in the code.
To set up tokens in Figma, start with the base value defined in the Colour Scheme collection. Once these are defined, you have all the different combinations of a variable and are ready to set up the collections you want to provide. Using the action variables as an example, you would first create the base values and then start with the variable’s last segment(collection). This is the taxonomy of our action variables: color/action/[appearance]/[prominence]/[state]. The last segment in this example would be the “state” collection. The state collection references the base values you defined in the Colour Scheme collection. In the state collection, you should define a variable for each mode in the previous segment; in this example, that would be all the modes you want to define in the prominence collection. Example: primary, secondary, tertiary. When this is ready, you continue to the segment before the prominence collection; in this example, this would be the appearance collection. The appearance collection is, in this example, the first segment and, therefore, the last collection you need to create. In this collection, you create all the variables you need. In our token taxonomy, we create a variable for different properties (text, icon, border, surface). These variables should reference the variables you created in the previous collection; in this example, that would be the specific appearance variables you created in the prominence collection.