Docs

This page isn't translated yet

Color tokens

Primitives, the color system, composed colors and colors with opacity.

On this page

Color tokens come in two tiers. Primitives are raw color scales; the color system maps them to semantic roles per color mode. Components reference the color system, so a change to a primitive or a mode reaches every component that uses it.

PrimitivesLink to this section

The colorPrimitives collection holds the raw scales: the base and theme ramps, including the endpoint stops 0 and 1000 as well as 50 through 950. Your export can add brand scales. Generate a ramp from one brand color with Color palettes.

Color systemLink to this section

The colorSystem collection has one mode per theme (default and dark with the default tokens). Each mode maps semantic roles such as foundations.bg or foundations.text to primitives, and component tokens reference those roles. A region switches modes with the theme prop or data-theme; see Themes and backgrounds.

References and overridesLink to this section

A DTCG color leaf carries $type: 'color' and either a structured sRGB $value or an alias such as {primitives.theme.600}. The extractor preserves references for the CSS pipeline, so semantic tokens can point to foundations and component states can point to those semantic roles. With the default tokens, layout.bg has main and alternative, typography has label, heading and body, and buttons have primary and secondary roles.

Use the semantic role for UI styling rather than choosing a ramp stop in each component. For example, bg-layout-main and color-body follow the current color system. A raw palette utility such as bg-theme-600 keeps the same primitive in both modes.

Override a runtime value in systhema.config, then run pnpm systhema-core sync:

import type { SysthemaConfig } from '@systhemaui/core'

const config: SysthemaConfig = {
  customTokens: {
    colorPrimitives: { theme: { 600: '#2563eb' } },
    colorSystem: { default: { foundations: { decorative: '{primitives.theme.600}' } } },
  },
}

export default config

The override is a plain runtime value, without $type, $value or the primitive file's primitives wrapper. See Custom tokens.

Colors with opacityLink to this section

Figma's "control opacity at scale" lets a color variable be another variable at N% opacity. Its export writes the flattened result in $value and keeps the two parts in $extensions["com.figma.composedColor"]. Core reads the parts, so the token stays a live reference instead of a frozen hex:

{
  "$type": "color",
  "$value": { "colorSpace": "srgb", "components": [0, 0, 0], "alpha": 0.25, "hex": "#000000" },
  "$extensions": {
    "com.figma.composedColor": {
      "colorArg": { "type": "alias", "alias": { "targetVariableName": "foundations/text" } },
      "opacityArg": { "type": "number", "value": 25 }
    }
  }
}
--color-foundations-text-muted: color-mix(in srgb, var(--color-foundations-text) 25%, transparent);

Change foundations.text, or switch to the dark mode, and every color composed from it follows. A flattened #00000040 would not.

The opacity can be a variable of its own, which is how one "muted" percentage drives a whole family. Point the composed color's opacity at a number variable (foundations/mutedOpacity, $value: 25) and core emits the reference, multiplied into a percentage:

--color-foundations-muted-opacity: 25;
--color-foundations-text-muted: color-mix(
  in srgb,
  var(--color-foundations-text) calc(var(--color-foundations-muted-opacity) * 1%),
  transparent
);

The color side can point anywhere: a colorPrimitives stop becomes var(--color-theme-600), a tailwindcss palette entry becomes a theme('colors.black') call.

Scope the opacity variable to Opacity in Figma, or use it as a same-collection composed-color opacity argument. A number variable is exported as a dimension (25px) unless its only Figma scope is Opacity, or a composed color in the same collection uses it as its opacity argument. A number that is neither reaches CSS as 25px and the calc(… * 1%) around it is invalid.

Nothing changes for a project whose tokens carry no composed colors: a color with a literal alpha still emits the 8-digit hex.

If the composed-color extension has no color alias or usable opacity argument, extraction falls back to the resolved $value. Cross-collection opacity aliases need the target variable's correct Opacity scope; the automatic unitless detection applies to same-collection references.

Generated tablesLink to this section

The color reference lists the shipped primitive and semantic values with their CSS variables. Use it to check exact paths and default values. The token collections page explains how modes reach the runtime tree.