PrimeStack.
Design·Nov 8, 2025

Design Systems in 2026: Tokens, Themes, and Tools

How design systems have evolved to support multi-brand, multi-platform ecosystems with automated token pipelines.


Design systems have evolved from static component libraries into living infrastructure. The design systems of 2026 aren't just about consistent buttons and typography scales — they power multi-brand product suites, support dark mode and high-contrast modes natively, and ship tokens to iOS, Android, CSS, and email templates from a single source of truth. Design tokens are the mechanism that makes all of this possible.

This article traces that evolution, explains how modern token architecture works, and covers the tooling, governance, and documentation practices that separate functional design systems from ones that quietly rot.

Table of Contents


The Evolution of Design Systems

The first generation of design systems were essentially Figma libraries with a matching CSS stylesheet. Designers maintained a collection of reusable Figma components; engineers maintained a parallel set of React components. Keeping them in sync was a manual, error-prone process. Design drift was constant.

The second generation introduced component libraries with documented APIs — Storybook became the standard. Engineers had a single source of truth for components, and designers worked from linked Figma libraries. The synchronization problem became less severe, but it didn't disappear. Color values still lived in two places: Figma fill styles and CSS variables in a constants file. When a brand refresh changed the primary blue, someone had to update both manually.

The third generation — where mature systems are today — is token-driven. Figma Variables (introduced in 2023 and stabilized throughout 2024) allow designers to define tokens directly in Figma. Style Dictionary transforms those tokens into platform-specific output formats. Components consume tokens rather than raw values. A brand refresh updates the token file; Style Dictionary regenerates CSS variables, Swift enums, and Compose constants; CI deploys the change. No one manually updates a color hex code in three places.


What Are Design Tokens?

Design tokens are named key-value pairs that represent a design decision. Instead of hardcoding color: #1a73e8 in a component, you reference a token: color: var(--color-action-primary). The token carries the value, and the token's value can change at the system level without touching component code.

The W3C Design Tokens Community Group has been developing a formal specification for token interchange format since 2021. The core of the spec is a JSON format with typed token values:

{
  "color": {
    "blue": {
      "500": {
        "$value": "#1a73e8",
        "$type": "color"
      }
    }
  }
}

The $type field is significant — it allows tools to understand what a token represents and apply appropriate transformations. A token of type color might be transformed to a hex string in CSS, a UIColor in Swift, and a Color resource in Android XML. A token of type duration maps to ms in CSS and TimeInterval in Swift.

The specification also defines composite token types for shadows, typography, and gradients, enabling richer token semantics than a flat string value.


The Three Tiers of Tokens

A well-structured token system has three tiers, each with a distinct purpose.

Tier 1: Primitive Tokens

Primitives are the raw material. They have no semantic meaning — they simply catalog every available value in the design language.

{
  "color-blue-100": "#e8f0fe",
  "color-blue-500": "#1a73e8",
  "color-blue-900": "#174ea6",
  "color-neutral-0": "#ffffff",
  "color-neutral-900": "#202124",
  "font-size-12": "0.75rem",
  "font-size-16": "1rem",
  "space-4": "0.25rem",
  "space-8": "0.5rem"
}

Primitives should never be used directly in components. Their purpose is to define the bounded set of values — if a designer wants a blue, they pick from this palette, not from an arbitrary hex input.

Tier 2: Semantic Tokens

Semantic tokens give meaning to primitives. They describe intent rather than value:

{
  "color-action-primary": "{color-blue-500}",
  "color-action-primary-hover": "{color-blue-900}",
  "color-surface-default": "{color-neutral-0}",
  "color-text-primary": "{color-neutral-900}",
  "color-text-secondary": "{color-neutral-600}"
}

Semantic tokens reference primitives using the aliasing syntax {token-name}. When the system resolves tokens, it follows aliases to their primitive values.

The critical property of semantic tokens: they are what changes between themes. In the default theme, color-action-primary resolves to color-blue-500. In a brand theme for a healthcare client, it resolves to color-teal-500. The component never changes — it always reads color-action-primary.

Tier 3: Component Tokens

Component tokens are the most specific tier and the most debated. They scope semantic tokens to a specific component:

{
  "button-background-primary": "{color-action-primary}",
  "button-text-primary": "{color-neutral-0}",
  "button-border-radius": "{radius-medium}",
  "button-padding-horizontal": "{space-16}"
}

Component tokens allow consuming teams to override specific component values without touching the component source. If a consumer needs their button corners to be sharper, they override button-border-radius rather than forking the component. This is a significant API surface advantage.

Not every design system needs component tokens. They add complexity and maintenance overhead. Teams with a single-brand, single-team system often stop at the semantic tier.


Multi-Brand Theming with Tokens

Token-driven theming is what allows a single component library to power multiple brand expressions. The architecture is straightforward: semantic tokens resolve to different primitives depending on the active theme.

In CSS, this is typically implemented through class-based or attribute-based themes at the root:

/* Default brand */
:root {
  --color-action-primary: #1a73e8;
  --color-surface-default: #ffffff;
}

/* Brand B theme */
[data-theme="brand-b"] {
  --color-action-primary: #00897b;
  --color-surface-default: #fafafa;
}

Switching brands at runtime is a single attribute toggle on the root element. No JavaScript re-renders. No component changes. The token values update through the cascade.

The more sophisticated implementation uses nested theming. A page can have a [data-theme="brand-b"] root while a promotional banner within it uses [data-theme="promo"]. Components inside the banner read --color-action-primary and get the promo values; everything outside gets the brand-b values. This is impossible with hardcoded color values or a single global constants file.


Dark Mode Done Right

Dark mode is where design systems reveal whether their token architecture is sound. A system built with semantic tokens can implement dark mode correctly. A system with hardcoded values cannot.

The naive approach — inverting lightness values or hardcoding a parallel color set in a .dark CSS class — fails because the relationship between colors isn't simply inverted. A surface that's white in light mode needs to be a specific dark gray in dark mode, not 255 - 255 = 0 (pure black). Elevated surfaces in dark mode are lighter than base surfaces, which is the opposite of light mode where elevation uses shadows.

The correct approach: define semantic tokens for both modes.

{
  "color-surface-default": {
    "light": "{color-neutral-0}",
    "dark": "{color-neutral-900}"
  },
  "color-surface-elevated": {
    "light": "{color-neutral-0}",
    "dark": "{color-neutral-800}"
  },
  "color-text-primary": {
    "light": "{color-neutral-900}",
    "dark": "{color-neutral-100}"
  }
}

Style Dictionary generates a light-mode variable set and a dark-mode variable set. The dark mode values apply via a prefers-color-scheme: dark media query or a [data-color-scheme="dark"] attribute. Components never reference color primitives; they always read semantic tokens. This means dark mode works automatically for every component the moment you wire up the CSS variable switch.

The failure mode to avoid: using semantic token names that embed mode assumptions. A token named color-white means nothing in dark mode — it implies a value, not an intent. A token named color-surface-default communicates intent and can resolve to any appropriate value in any mode.


The Token Pipeline

The token pipeline transforms design source (Figma Variables) into platform-specific output through automation. The canonical stack in 2025 is:

Figma VariablesToken export pluginJSON token filesStyle DictionaryCSS / Swift / Kotlin / JSON outputs

Figma's native Variables API allows plugins to export all defined variables. The Tokens Studio plugin (formerly Figma Tokens) has been the leading tool for this workflow, with support for GitHub sync that pushes exported JSON directly to a repository.

Style Dictionary is the transform engine. It reads your token JSON, applies transforms (converting color values to platform-native formats, converting spacing values from pixels to points for iOS), and runs formatters that write output files in the target syntax.

A minimal Style Dictionary config:

export default {
  source: ["tokens/**/*.json"],
  platforms: {
    css: {
      transformGroup: "css",
      prefix: "ps",
      buildPath: "dist/css/",
      files: [{ destination: "tokens.css", format: "css/variables" }],
    },
    ios: {
      transformGroup: "ios-swift",
      buildPath: "dist/ios/",
      files: [{ destination: "StyleDictionary.swift", format: "ios-swift/enum.swift" }],
    },
    android: {
      transformGroup: "android",
      buildPath: "dist/android/",
      files: [{ destination: "tokens.xml", format: "android/resources" }],
    },
  },
};

This pipeline runs in CI on every merge to the design tokens repository. Downstream repositories consume the published package and get token updates through standard dependency management — the same workflow as any other dependency upgrade.


Component API Design

Tokens handle the visual layer. Component API design handles the structural layer.

Compound Components

Compound components use React context to share state between a parent and several children, allowing flexible composition without prop drilling:

<Select value={value} onChange={setValue}>
  <Select.Trigger />
  <Select.Content>
    <Select.Item value="a">Option A</Select.Item>
    <Select.Item value="b">Option B</Select.Item>
  </Select.Content>
</Select>

Each subcomponent is a named export on the parent. State lives in the <Select> root; children read it via context. This pattern avoids the "props explosion" problem where a single component accumulates dozens of configuration props.

Slots Pattern

The slots pattern provides named composition points within a component, similar to Web Components slots:

<Card
  slots={{
    header: <CardTitle>My Card</CardTitle>,
    media: <img src="..." alt="..." />,
    footer: <Button>Action</Button>,
  }}
/>

Slots are more opinionated than render props but less opinionated than predefined children structures. They work well in design systems where the layout is fixed but the content is variable.

Polymorphic Components

A polymorphic component renders as different HTML elements depending on the as prop, while preserving type safety:

<Text as="h1" size="2xl">Page Title</Text>
<Text as="p" size="md">Body text</Text>
<Text as="label" size="sm" htmlFor="email">Email</Text>

The TypeScript implementation uses the as prop to determine which HTML element's props are valid. <Text as="label"> accepts htmlFor; <Text as="input"> accepts value and onChange. This is more composable than separate Heading, Paragraph, and Label components and avoids semantic HTML errors.


Testing Design Systems

Visual Regression Testing

Visual regression testing compares screenshots of components at every commit and flags changes. Chromatic (the cloud service for Storybook) automates this workflow: on every pull request, it renders all Storybook stories, compares them to the baseline, and surfaces visual diffs for review.

The discipline required: every component variation should have a dedicated Storybook story. Components without stories aren't covered by visual regression. Over time, a design system without comprehensive story coverage accumulates visual regressions that aren't caught until production.

Accessibility Audits

Axe-core integrated into Storybook via @storybook/addon-a11y runs automated WCAG audits in the component browser. This catches the majority of mechanical accessibility failures: missing alt text, insufficient color contrast, invalid ARIA attributes, missing form labels.

Automated testing catches perhaps 30–40% of real-world accessibility issues. The rest require manual testing: keyboard navigation flows, screen reader behavior, focus management in modal and drawer patterns. Design systems should document keyboard interaction patterns and include them in the component spec.


Documentation That Developers Use

The most common design system failure mode is good components with unusable documentation. Developers who can't quickly understand how to use a component either use it incorrectly or avoid it and write their own.

Effective documentation has three layers:

Live examples. Code rendered alongside the output, with a copy button. Developers read code, not prose. If the first thing a developer sees is three paragraphs about the component's design philosophy, they've already opened a new tab to look for examples elsewhere.

Props table. Every prop with its type, default value, and a one-line description. Auto-generated from TypeScript types where possible — manually maintained props tables drift from implementation.

Usage guidelines. When to use this component and when not to. What common mistakes to avoid. Which accessibility considerations the component handles automatically and which the consumer is responsible for. This is the layer that separates documentation from a props dump.

The Storybook MDX format allows combining live examples and prose in a single document. Combining this with automatic props extraction from TypeScript types reduces the documentation maintenance burden substantially.


Governance Model

A design system without governance is a dependency that grows unpredictably and breaks consuming applications. Governance answers: who can contribute, how changes are reviewed, how versions are numbered, and how breaking changes are communicated.

Contribution guidelines define the bar for adding a new component. The typical question: "Is this component used in at least three distinct features by at least two different teams?" Components added before meeting this threshold tend to be over-fit to their original context and require immediate breaking changes when a second consumer needs them.

Versioning follows semantic versioning strictly. Any change to a component's public API — adding required props, removing props, changing event signatures — is a major version bump. Adding optional props with defaults is a minor bump. Bug fixes and visual tweaks within token constraints are patch bumps. The definition of "breaking change" must be documented and applied consistently, or semver loses its meaning.

Breaking change management requires more than a version number. A migration guide, a codemod where feasible, and a deprecation period (typically one major version) give consumers time to adapt. Announcing breaking changes through your changelog, a team Slack channel, and office hours reduces the surprise factor that causes consuming teams to freeze on old versions.

The core tension in design system governance is between velocity and stability. The design system team wants to ship improvements; consuming teams want a stable dependency. The resolution is a clear distinction between the stable public API (semver-protected) and the internal implementation (which can change freely). Components that aren't ready for the stable API ship in an unstable_ namespace — available for early adopters but with no semver guarantees.