Design token — the shape, the architecture, the integrations

Status: accepted · Owner: Head of Design · Scope: Aleris Group · Version: v0.1 · Updated: 2026-08-10

Author: Torfinn · Audience: product teams, designers, and the assistant

Supersedes: — · Related: [[principles/design-tokens]], [[data-products/tokens/aleris-tokens.css]]

This is the schema — the shape of a token record, how the token layers compose, and how other systems (frameworks, Figma) consume them. It does not restate any token's value: values live in data-products/tokens/aleris-tokens.css (source of truth) and data-products/tokens/baseline-tokens.json (machine-readable twin), and a document that repeats a value here is a document that can drift from them — which is the failure constitutional/tokens-are-canonical.md exists to prevent, and the one this file's predecessor spent four months committing.

The reasoning behind why a value is what it is — why petrol/orange/sand, why a perfect-fifth type scale, why cards are 16px — lives in [[principles/design-tokens]], not here. This file is the shape; that file is the argument.


The shape of a token record

Every custom property in aleris-tokens.css is documented inline, in the comment on its own declaration line:

--radius-l: 16px;  /* @usage Cards only | @constraint Card tier, decided 2026-08-10 (board card 45) from a live production measurement. Not a general container value */

A record has:

  • name — the --custom-property itself.
  • value — a literal, or a var() reference to a lower layer.
  • @usage — what it's for, in plain language.
  • @constraint — what it must not be used for, when that matters.

data-products/tokens/generate-tokens-json.js parses this convention directly and produces baseline-tokens.json: every record as { value, usage, constraint }, organized by layer and category. Not every token carries @usage/@constraint today — annotation coverage is partial and grows opportunistically, not a completeness requirement. Consult the JSON for lookup; the CSS remains the source of truth if the two ever disagree (regenerate the JSON, per its own header comment).


Three-layer architecture

Aleris tokens use a three-layer model — the same shape IBM Carbon, Google Material, Salesforce Lightning and Atlassian converge on.

Layer 1 — Primitives. Raw values: hex codes, pixel values, font names, ratios. The complete Aleris visual palette. Almost never referenced directly in product code.

Layer 2 — Semantic tokens. Primitives mapped to usage context. --text-primary rather than --color-petrol-500. These carry meaning — where a value should be used and why. This is the layer product teams reference in code.

Layer 3 — Component tokens. Specific to individual UI components. --button-primary-bg rather than --brand-accent. These matter when multiple teams build against the same codebase, or when theming.

Usage: reference semantic tokens (Layer 2) in code. Use component tokens (Layer 3) when they exist for your component. Never reference primitives directly — typing a hex code is the signal something is wrong.

Each layer references the one below it; no layer requires the one above it to function. A product implementing only primitives still gets a coherent Aleris palette. This is Baseline's expression of Aleris's progressive-enhancement principle, and it has a practical payoff: change one primitive, and every semantic and component token that references it updates automatically. Reference a primitive directly instead, and you've created a manual update point that will drift — the same failure mode constitutional/tokens-are-canonical.md generalizes from incidents found the hard way (cards 8, 21, 22, 24).


Usage guidance, by category

Practical starting points — not values, which live in the token file, and not reasoning, which lives in [[principles/design-tokens]].

Color. Use --text-primary for nearly everything; reach for --text-secondary only with a clear hierarchy reason — more than two text colors on a screen is a sign to reconsider the hierarchy, not add a third token. Status colors (--status-*, --goal-*) communicate state only, never decoration. Goal-status colors always pair with an icon or shape (checkmark, warning triangle, X, dash) — colour alone never carries the meaning, per constitutional/accessibility-is-foundational.md.

Typography. --font-weight-regular (500) for body, --font-weight-bold (700) for headings covers almost everything; --font-weight-light (300) and --font-weight-black (900) are for sparing, deliberate emphasis. Never font-weight: 400 — Museo Sans doesn't have it, and the browser will synthesize it badly. Most pages use h2 for the page title, h3 for sections, h4 for card headers, body for the rest; h1 (60px) is for communicative surfaces only (landing pages, hero areas) and most instrumental interfaces won't reach for it. Never skip heading levels. Match icon size to the text it accompanies — body gets --icon-md (18px), labels get --icon-sm (16px).

Radius. Cards get --radius-l. Other containers get --radius-s. Buttons and inputs get --radius-m. Badges and tags get --radius-full. That covers 90% of cases — see BASELINE.md § Component shapes for the corner-radius reasoning behind the card exception.

Buttons. One orange (primary) button per screen. A second action is outline or ghost, never a second orange. Two orange buttons visible at once means one of them is wrong.

Spacing. Communicative surfaces use the full range (--spacing-sm through --spacing-3xl); instrumental surfaces use the tighter range (--spacing-3xs through --spacing-lg). Same tokens, different slice.

Charts. Use --chart-1 through --chart-N in sequence, teal first. Beyond 6–8 colors in one chart, the problem is the visualization, not the palette — simplify the data grouping before reaching for chart-13.


Framework bridges

Baseline tokens are plain CSS custom properties. They don't depend on Tailwind, React, or any other framework to function. A product that imports aleris-tokens.css into a static HTML page gets the full Aleris design system; a product that imports it into a Next.js app with Tailwind gets the same tokens, plus the option to use utility classes through a bridge layer. This follows the same progressive-enhancement principle as the three-layer architecture above: the base layer is complete on its own, and additional layers make development faster without being load-bearing.

A bridge is a thin mapping file that translates Baseline tokens into whatever utility system a project uses. The dependency is one-way — the bridge reads from tokens, tokens never reference the bridge — so a project that drops the bridge loses nothing the tokens didn't already provide directly.

Tailwind CSS v4

The bridge is generated. Do not hand-write one. data-products/tokens/aleris-tailwind.css is produced from the token file by data-products/tokens/generate-tailwind-theme.js and asserted by data-products/tokens/tailwind-bridge.test.ts. Import it after the tokens:

@import "./aleris-tokens.css";
@import "./aleris-tailwind.css";
@import "tailwindcss";

That gives bg-aleris-petrol-500, text-aleris-text-secondary, p-aleris-md, rounded-aleris-m, shadow-aleris-e1, leading-aleris-body, and the aleris-md: breakpoint variant — 145 of the token file's 363 tokens. Each compiles straight to the token: bg-aleris-petrol-500 emits background-color: var(--color-petrol-500). The bridge adds nothing to :root, so dropping it removes utilities and no values. Which families are not bridged, and why each one is out, is listed in the generated file's own header rather than here — a partial mapping that does not say which part is a mapping that reads as complete.

Two things changed on 2026-08-28 (card 97), both worth knowing if you have the old block in a project.

It is generated because the hand-written version drifted. The example this section used to carry referenced 25 tokens, and 7 of them did not exist: --elevation-1/2/3 and --font-size-h1/h2/h3/h4, against a token file that declares --shadow-e0..e3, --elevation-card/dropdown/flat/modal, and --font-size-xs..2xl. A consumer copying that block got seven utilities resolving silently to nothing. This is _packages/README.md rule 5 in a different costume — a hand-maintained mapping goes stale, and nothing tells you.

Every name now carries an aleris- prefix, and that is a real change in ergonomics. The old block replaced Tailwind's own namespace — --color-petrol, --spacing-4, --radius-lg — so you wrote bg-petrol and p-4. The generated bridge adds alongside it, so you write bg-aleris-petrol-500 and p-aleris-md and Tailwind's defaults keep working. The reason is measured, not stylistic: against Tailwind 4.2.2, a theme variable whose name matches Tailwind's own namespace is emitted into @layer theme as a self-reference (--color-petrol-500: var(--color-petrol-500)), which is invalid at computed-value time and survives today only because aleris-tokens.css is normally imported unlayered. A project that imports the tokens into a cascade layer would get empty values with no error. The prefix removes the collision and the dependency on that accident; it also makes bg-aleris-* visibly a token where bg-[#004851] is visibly not.

What this would make wrong: if the shorter bg-petrol form matters more than the safety, the identity map can be restored — but only for names that do not collide with a Tailwind namespace, and the generator should then be the thing that enforces that rather than a reviewer. --color-*, --spacing-*, --radius-*, --shadow-*, --breakpoint-* and --ease-* all collide today.

The old approximation trade-off is gone with the old approach. Because the bridge no longer remaps Tailwind's numeric scales onto Baseline's, there is nothing left to approximate: text-aleris-base is --font-size-base exactly, and Tailwind's own text-base is untouched and still means what Tailwind's docs say. The reasoning that governed the old mapping is preserved in [[principles/design-tokens]] §14, which is about a bridge that renames; it no longer describes this file.

Tailwind CSS v3 (legacy)

// Extend, don't replace. Never use default Tailwind colors.
module.exports = {
  theme: {
    extend: {
      colors: {
        petrol: { 100: '#d9e1e2', 300: '#7fa9ae', 500: '#004851' },
        sand: { 100: '#f2ece4', 300: '#e7ceb5', 500: '#d9b48f' },
        orange: { 100: '#fde8df', 300: '#ffbe9f', 500: '#f58c61' },
        gray: { 100: '#d7d2cb', 300: '#9e9281', 500: '#585044' },
        error: '#c14444',
        warning: '#d9b48f',
        goal: {
          achieved: '#2e8540',
          borderline: '#ffb81c',
          missed: '#d4351c',
          'no-data': '#007bc7',
        },
        chart: {
          1: '#0f9081', 2: '#94cbc4', 3: '#577ba3', 4: '#98c9ef',
          5: '#a078c2', 6: '#d8b8ef', 7: '#d77a61', 8: '#f0c3b2',
          9: '#6b9495', 10: '#bed0c0', 11: '#e4ded5', 12: '#d9b48f',
        },
      },
      fontFamily: {
        sans: ['Museo Sans', 'Arial', 'sans-serif'],
      },
      fontSize: {
        // Perfect fifth scale (1.5) from 18px base
        xs: '0.78rem',    // 14px — accessibility floor
        sm: '0.89rem',    // 16px — labels, UI chrome
        base: '1rem',     // 18px — body text (scale anchor)
        md: '1.22rem',    // 22px — h4, card headers (half-step)
        lg: '1.5rem',     // 27px — h3 (×1.5)
        xl: '2.22rem',    // 40px — h2 (×1.5²)
        '2xl': '3.33rem', // 60px — h1 (×1.5³)
      },
      spacing: {
        // Perfect fifth scale (1.5) aligned to 4px grid
        '3xs': '4px', '2xs': '8px', xs: '12px', sm: '16px',
        md: '24px', lg: '36px', xl: '48px', '2xl': '72px', '3xl': '96px',
      },
      borderRadius: {
        DEFAULT: '8px',
        lg: '16px',
        full: '100px',
      },
      boxShadow: {
        soft: '0px 2px 8px rgba(0, 72, 81, 0.08)',
        medium: '0px 4px 16px rgba(0, 72, 81, 0.12)',
        strong: '0px 6px 24px rgba(0, 72, 81, 0.16)',
      },
    },
  },
}

Vanilla CSS

Reference aleris-tokens.css directly via import or link. All tokens are CSS custom properties on :root and work in any CSS environment — no bridge needed, no framework dependency.

@import url('aleris-tokens.css');

body {
  background-color: var(--surface-page);
  color: var(--text-primary);
  font-family: var(--font-family-primary);
  font-size: var(--type-body-size);
  line-height: var(--type-body-line-height);
}

Adding a new bridge

  1. Create a mapping file that reads from aleris-tokens.css and translates to the framework's conventions.
  2. Keep the dependency one-way: the bridge reads from tokens, tokens never reference the bridge.
  3. Document where the mapping is exact and where it approximates.
  4. Treat the bridge as project-level infrastructure, not part of the design system itself.

Candidate future bridges: a Figma variables sync, a PDF generation stylesheet, an email-safe CSS subset, a native mobile token export.


Figma sync notes

Changes made to the Figma export values that still need syncing back to Figma, to maintain single-source-of-truth integrity:

Change Figma value Canonical value Reason
Full type scale replacement title 40px, h1 32px, h2 22px, h3 18px h1 60px, h2 40px, h3 27px, h4 22px Rebuilt on perfect fifth (1.5) modular scale. Eliminates h3/body collision. Unified ratio with spacing.
Spacing scale replacement 4,8,12,16,24,32,40,48,64,240 4,8,12,16,24,36,48,72,96 Rebuilt on 1.5 ratio aligned to 4px grid. Shared ratio with type scale.
Sizes below 14px label-small 12px, card-comment 13px, button-label-s 13px Removed entirely Below 14px accessibility floor.
Duplicate token sets global--tokens---museo--sans---* Removed Identical values with auto-generated Figma naming.
Icon size tokens Mixed into text-size styles Separated to --icon-s/m/l/xl Icons are not typography.
Font Awesome tokens global--tokens---font--awesome-6--pro---* Removed Implementation detail, not design token.
Shadow values (e1-e3) black-based rgba petrol-based rgba Brand coherence with warm palette.
radius-m Not in Figma 8px Needed for input components.
radius-l Not in Figma 16px Retired 2026-08-07, reinstated 2026-08-10 (board card 45) at 16px for cards. Needed for card components — not yet in Figma.
warning-100 naming warning-100 error-500 The value (#c14444) is an error red, not a warning amber.
warning amber Not in Figma warning-500 (#d9b48f) Distinct warning color in sand family.

File structure

data-products/
└── tokens/
    ├── aleris-tokens.css          ← Source of truth (Layer 1)
    ├── aleris-fonts.css           ← Museo Sans @font-face
    ├── baseline-tokens.json       ← Generated machine-readable lookup
    ├── generate-tokens-json.js    ← Regenerates the JSON from the CSS
    └── tokens.test.ts             ← Fitness functions: contrast, retirement guards,
                                      reference-drift, nesting inversions
schemas/
└── design-token.md                ← This file — shape, architecture, integration
principles/
└── design-tokens.md               ← Why each value is what it is
baseline/
├── BASELINE.md                    ← AI instructions (this system's entry point)
├── buttons/, conformance/         ← Rendered fitness surfaces
├── governance/
│   └── aleris-design-governance.md
└── voice/                         ← "Den nära experten" voice guides

Moved 2026-08-10 (Phase B step 3, board cards 46/48) from baseline/tokens/ and baseline/reference/aleris-design-tokens.md + baseline/reference/baseline-token-architecture.md (the latter two merged and split into this file and principles/design-tokens.md). Published routes under /baseline/... are unchanged — lib/baseline-content.ts's resolveBaselineSource resolves the moved nav items to their new locations, the same mechanism that kept patterns/ publishing at /baseline/patterns/* after its own move (card 32).


What this means for new projects

Starting a new Aleris product:

  1. Import data-products/tokens/aleris-tokens.css. This gives you the full design system as CSS custom properties.
  2. If your project uses Tailwind, also import an aleris-tailwind-bridge.css built from the Tailwind v4 example above. This gives you utility classes that resolve to Aleris values.
  3. Write component styles using var() references for semantically important values (colors, typography, surfaces).
  4. Use Tailwind utilities for layout mechanics (flex, grid, responsive, spacing) where the bridge is active.
  5. Never hard-code values. If a value doesn't exist in the token file, flag it as an open question.

Starting a project without Tailwind:

  1. Import data-products/tokens/aleris-tokens.css. You have everything you need.
  2. Write CSS that references tokens via var().
  3. Use modern CSS features (nesting, cascade layers, container queries) for layout.
  4. The design system works identically — no bridge needed, no framework dependency.

Related

  • [[principles/design-tokens]] — why each value is what it is
  • [[data-products/tokens/aleris-tokens.css]] — the canonical values
  • [[data-products/tokens/baseline-tokens.json]] — the machine-readable twin
  • [[constitutional/tokens-are-canonical.md]] — the rule this schema exists in service of: name the token, don't restate its value
  • baseline/BASELINE.md § Token system — the AI-facing entry point

Read this page as markdown