Token Values
Design tokens are named design decisions (colors, spacing, type scale, shadows, etc.) stored as data. They keep visual design consistent across products and enable runtime theming and multi-brand support.
What are design tokens?
Design tokens are variables that hold design values. Instead of hard-coding #f1494f or 16px in code, we use token names such as color.primitives.brand.500 or spacing.semantics.spacing_md. Tokens live in JSON (and can be compiled to CSS or app config) so that:
- Design and code stay in sync.
- Theming is done by swapping token sets, not editing components.
- Accessibility and brand overrides are centralized.
Primitive vs semantic tokens
Primitive tokens are the raw scale: hex colors, pixel sizes, font families, shadow definitions. They have no product meaning by themselves (e.g. brand.600, size_16).
Semantic tokens give meaning for use in the product: “primary button background”, “body text”, “card padding”. They reference primitives (or other semantics) so that changing a primitive updates every semantic that points to it.
Example: a semantic token button.background.primary might reference {color.primitives.brand.600}. For a different brand, only the primitive (or theme override) changes; the semantic name stays the same.
Token value formats
Tokens use a few value types:
- Hex — e.g.
#f1494f,#18181b1a(with alpha). - RGBA — sometimes used in exports; we normalize to hex where possible.
- References (aliases) — strings that point at another token, e.g.
{color.primitives.brand.600}or{dimensions.primitives.size_16}. References are resolved at build or runtime; they are not inlined in the token files so theming stays flexible.
Dimensions and spacing use values with units in primitives (e.g. "16px") and references in semantics.
Referencing primitives in semantics
Semantics reference primitives with a dot-path string so a resolver can substitute the value later.
Pattern: {category.primitives.path.to.token}
Examples:
{color.primitives.brand.600}→ resolves to the brand 600 hex (e.g.#d54045).{color.primitives.neutral.1000}→ primary text color.{typography.primitives.fontSize.sm}→ 14 (or 14px depending on pipeline).{shadows.primitives.sm}→ the “sm” shadow definition (array of shadow objects).
Do not resolve these to raw hex or pixels in the token JSON; keep them as references so theme overrides can replace primitives without touching semantics.
Example: spacing semantics referencing primitives
Our spacing semantics point at the dimension scale so one scale drives all spacing.
Primitives (dimensions):
{
"size_2": "2px",
"size_4": "4px",
"size_6": "6px",
"size_8": "8px",
"size_12": "12px",
"size_16": "16px",
"size_20": "20px",
"size_24": "24px",
"size_32": "32px",
"size_40": "40px"
}
Semantics (spacing):
{
"spacing_4xs": "{dimensions.primitives.size_2}",
"spacing_3xs": "{dimensions.primitives.size_4}",
"spacing_2xs": "{dimensions.primitives.size_6}",
"spacing_xs": "{dimensions.primitives.size_8}",
"spacing_sm": "{dimensions.primitives.size_12}",
"spacing_md": "{dimensions.primitives.size_16}",
"spacing_lg": "{dimensions.primitives.size_20}",
"spacing_xl": "{dimensions.primitives.size_24}",
"spacing_2xl": "{dimensions.primitives.size_32}",
"spacing_3xl": "{dimensions.primitives.size_40}"
}
Component spacing (e.g. button padding, cell padding) then references these semantics (e.g. {spacing.semantics.spacing_sm}), so the whole chain stays driven by the primitive scale.
Best practices
- Single source for scales — One primitive scale per concern (color, dimensions, radius, etc.). Semantics and component tokens only reference them.
- No raw values in semantics — Prefer
{color.primitives.brand.600}over#d54045in semantic tokens so theming works. - Naming — Primitives: scale names (e.g.
brand.600,size_16). Semantics: intent (e.g.foreground.defaults.primary,spacing_sm). - Theme overrides — Per-brand or per-mode overrides only replace primitives (or add new ones). Semantic names stay the same.
- Units in primitives — Store values with units (e.g.
"16px") in the primitive layer; the pipeline can strip units for JS or keep them for CSS.
Troubleshooting
- Reference not resolving — Check the path: it must match the actual token path (e.g.
dimensions.primitives.size_12notspacing.primitives.size_12if the scale lives under dimensions). Resolver must load primitives before semantics. - Wrong value after theme switch — Ensure the theme override only overrides primitives and that semantics never hard-code hex or px for themeable tokens.
- Missing token — Confirm the key exists in the referenced file (e.g.
foundation/primitives/dimensions.json) and that the alias string is exactly{category.primitives.key.path}with no typos. - Circular references — Semantics should reference only primitives (or other semantics that eventually point to primitives). Avoid semantics that reference each other in a loop.