Problem/Motivation

After SDC in Drupal 10.1 (July 2023), and the Icon API in Drupal 11.1 (Dec 2025), we are continuing to add design systems API in Core with #3517033: Add a style utility API, in order to be able to build business agnostic, shareable, Drupal themes, providing design implementations which can be leveraged by display building tools.

There is a last design system API to cover the main parts of design systems: CSS variables.

Initially, we were targeting 2026, but an exciting discussion with XB's team in DrupalDevDays 2025 (they wanted a way of altering an utility while applying it) made us realise it will be beneficial to ship it alongside the Style API.

In Web implementations of design systems, CSS variables (sometimes referred as custom properties or cascading variables) are

  • typed
  • an (often small) subset of the design tokens which are overridable at runtime. So, the new value is applied by the browser and not by rebuilding the CSS.
  • defined in the design system implementation (so, in the Drupal theme). Some design system are strongly branded and restrictive with their CSS variables. Other have a liberal approach. We must allow any kind of approach in our API.
  • living at the page level, but they can be overridden with a CSS selector as a scope.
  • names solely according to authors and users conventions; CSS will never give them a meaning beyond what is presented here.

CSS variables are a commonly found in design systems. Examples:

Design Tokens are the smallest unit of design:

  • They point to style values like colors, fonts, and measurements
  • Components, style utilities, icons, grid systems... are made of tokens
  • Each of them is named for how or where it’s used. Even if a token’s end value is changed, its name and use remain the same
  • They can be extracted from the designer tool ans shared between many implementations. In a Web implementation, they can be resolved at buildtime (Sass variable, Tailiwind's @apply directive...) or exposed at runtime (CSS variables).

Analysis of the current solutions in the contrib space

Like SDC, the Icon API and the upcoming Style API, we believe it must be front-dev friendly, UI logic focused, YAML plugin declaration available in Drupal theme. So, let's have a look on contrib modules are already covering this scope.

UI Skins (usage: 330)

https://www.drupal.org/project/ui_skins

Discovery: {provider}.ui_skins.css_variables.yml in modules and themes.

Example (Material 2 background color variables):

mdc_theme_background:
  label: "Default background color"
  category: Background colors
  type: color
  default_values:
    ":root": "#ffffff"

mdc_theme_surface:
  label: "Surface background color"
  category: Background colors
  type: color
  default_values:
    ":root": "#ffffff"

You can override the variables values (by scope) in theme settings. The overridden values are added in a style element to the page header.

Personal opinion:

I am maintainer of this module and I participated to define this format. So I believe this format could be one of the starting point. However, there is a few issues we can fix in this proposal:

Design Tokens

https://www.drupal.org/project/design_tokens

A fascinating proof-of-concept from the e0ipso, the creator of SDC

Design Tokens are be declared:

Example (in YAML, but the standard serialization is JSON):

shadow-token:
  "$type": shadow
  "$value":
    color: "#00000080"
    offsetX:
      value: 0.5
      unit: rem
    offsetY:
      value: 0.5
      unit: rem
    blur:
      value: 1.5
      unit: rem
    spread:
      value: 0
      unit: rem
font-size:
  "$value":
    value: 3
    unit: rem
  "$type": dimension

You can override the token values with config entities implementing DesignTokenInterface. The override values are published in a dynamically generated CSS file (see LibraryStyleController and CssVarsSerializer

Personal opinion:

An other great starting point . The dependency to jsonrpc must be skipped for Core inclusion. The idea of using directly DTCG is as great as using Json Schema for SDC props. However, we need to clarify:

  • what is happening if we get a DTCG files which is describing all tokens, event the ones not exposed as CSS variables? Or if the CSS variables are not exactly names like the tokens (prefix, format...)?
  • do we need other (meta)data?
  • how do we treat CSS variables scopes? No default values in the theme and we add only in the override config entity?

CSS Variables (usage: 10)

https://www.drupal.org/project/cssvars

Not compatible with Drupal 11.

No definition format.

Personal opinion:

If my understanding is right, CSS Variables are not really integrated with Drupal. It is only a form with a textarea where people put free values which are attached to page renderable.

CSS color variables (usage: 4)

Works only for colors, but use CSS variables anyway.

Not a YAML plugin discovery but a PHP file to add in the theme, so not front-dev friendly enough: https://git.drupalcode.org/project/css_color_variables/-/blob/1.0.x/colo...

[
  // Available colors and color labels used in theme.
  'fields' => [
    'primary' => t('Primary'),
    'primary__contrast' => t('Primary contrast'),
    'secondary' => t('Socondary'),
    'secondary__contrast' => t('Socondary contrast'),
      ...
  ],
  // Pre_defined color schemes.
  'schemes' => [
    'default' => [
      'title' => t('deGov default'),
      'colors' => [
        'primary' => '#004673',
        'primary__contrast' => '#ffffff',
        'secondary' => '#818D97',
        'secondary__contrast' => '#ffffff',
      ],
    ],
]

Personal opinion:

Value are not scoped/scopable. Predefined schemes are interesting, but let's keep them out of this scope. We may discuss this in a future ticket.

Honorable mentions

Proposed resolution

Definition & discovery

Based on the analysis below, with some discussions.

For example, it seems it would be a mistake to rely only on automatic discovery from CSS files to get the list of CSS variables, because we will miss the metadata, and because not all CSS variables from a codebase must be exposed to tyhe Drupal CMS. For example, Bootstrap 5 did the mistake of converting all Sass variables (build-time tokens) to CSS variables (runtime tokens) and we need to cherry-pick them.

In the renderer service

There is two expected usage of those variables in the render API.

1. Local usage. Modules can leverage this API by overriding variables values in $build["#attributes"]["style"]. However, this is causing a few issues:

  • The syntax is verbose and error prone
  • Variables values are mixed with other inline styles
  • There is no possibility to add checks about the existence of a variable, or the correctness of the value.

So, it would be better to introduce #variables (or #tokens) universal property, which can be added to every renderables already accepting an #attributes property:

  • ['#type' => 'html_tag']
  • ['#type' => 'component']
  • Most of #theme and most of render elements

This is excluding #markup, #plain_text and maybe some #theme and some render elements.

Example:

$b['#variables'] = [
  'primary-color' => '#2341AB',
]

or:

$b['#tokens'] = [
  'primary-color' => '#2341AB',
]

If we implement also component-specific variables, it will be the opportunity of processing a check to see if the variable exists when #variables (or #tokens) is added to a SDC render element.

So the renderer service to process this by adding the variables/values pair to $build['#attributes']['style']

2. Global usage. Modules can leverage this API by overriding variables values at the page level, leveraging the scope, like UI Skins module is currently doing: https://git.drupalcode.org/project/ui_skins/-/blob/1.1.x/src/HookHandler...

What do we do? We extend #attached render property with a variable (or token) keyword? We extends our own #variables render property? We add an other universal render property?

Example:

$b['#attached']['variable']['primary-color'][':root'] = '#EF2411',

Or:

$b['#attached']['token']['primary-color'][':root'] = '#EF2411',

Remaining tasks

Let's start by contacting the maintainer of the contrib modules to ask them if they want to participate.

User interface changes

No. API only.

Introduced terminology

"CSS variables", "custom properties", "design tokens", "runtime tokens", "cascading variables", "CSS cscope"

API changes

Only additions.

Data model changes

No.

Issue fork drupal-3531854

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

pdureau created an issue. See original summary.

pdureau’s picture

Issue summary: View changes

pdureau credited d34dman.

pdureau’s picture

Title: Add a CSS variables API » Add a Design Tokens & CSS variables API
Issue summary: View changes

Following discussions with @d34dman and @4aficiona2 in #3533198: [Meta] Make Drupal the first "design-system native" CMS + Unify & simplify render & theme systems and with @e0ipso and @penyaskito on slack, I have renamed the issue and added a description of https://www.drupal.org/project/design_tokens

We now have 2 great starting points:

ui_skins design_tokens
Maturity Used successfully in production by many website for years, but needs to be modernize and cleanup A new, fresh, proposal. Cleaner but not polished by time yet.
Theme wide tokens ✅ yes, in a Drupal specific format (with the plan of adding DCTG stuff) ✅Yes, a pure DCTG file
Component specific tokens ⚠️ not really, they are defined with the others ✅Yes, directly in SDC file
Value override in config ✅ in theme settings, with scope management, only overridden values are printed ⚠️ as config entities (which is better than theme settings), but no scope management
Render API additions ❌ no ❌ no
dalemoore’s picture

I just discovered this concept while trying to piece together my own design system.

For the design tokens, is the idea for the theme-level ones to be contained within themename.tokens.yml (or modulename.tokens.yml)?

The draft spec seems to recommend .tokens or .tokens.json, so I assume ours would be .tokens.yml

pdureau’s picture

The draft spec seems to recommend .tokens or .tokens.json, so I assume ours would be .tokens.yml

If we go the DTCG way, I would recommend to support both YAML and JSON format.

Our plugin discovery will be YAML only, using the common Drupal\Core\Plugin\Discovery\YamlDiscovery:

  • So, filename will be: {provider}.tokens.yml
  • The data structure will strictly follow DTCG
  • That's neat because the first level is a mapping where the keys are our plugin ID:
shadow:
  "$type": shadow
  "$value": { .. }
font-size:
  "$type": dimension
  "$value": {...}

Because JSON is a subset of YAML (not exactly but it is enough for us), front developers can simply copy paste the content of .tokens.json or .tokens file provided by upstream inside the YAML and it works.

That would be my recommendation: only YAML files but JSON content allowed inside. But we can go further, and also do a multiple YAML discovery using decorators to support those 3 filenames:

  • {provider}.tokens.yml
  • {provider}.tokens.json (or {anything}.tokens.json)?
  • {provider}.tokens (or {anything}.tokens?)

We will need to discuss precedence of identical plugin IDs in those files.

pdureau’s picture

So, here we go...

Target: 11.3

Freeze is in 2 weeks (26th October), so let's target a minimal scope addressing the ecosystem :

  • OK enough for contrib modules to start experimentation and to secure a common format.
  • Not yet OK to be used in customer projects

With:

  • use of standard DTCG format (inspired by Design Tokens module)
  • each token is loaded as a plugin by a plugin manager (inspired by UI Skins) with:
    • a DTCG parser will extract the plugin list from the nested group structure of DTCG (inspired by Design Tokens module)
    • a config schema for this plugin type (no need for a JSON schema because must exists in the wild)
  • a CSS variables serializer (inspired by Design Tokens module but implementing SerializationInterface)
  • a (still minimal, no handlers yet) config entity to override the value of each token by scope, not automatically generated (a mix of Design Tokens and UI Skins modules)
  • unit & kernel tests

From DCTG, we cover the minimum for working tokens:

  • groups and nested groups
  • A first bunch of token types: Dimension, Font family, Font weight, Number, Typography

See: https://www.designtokens.org/tr/drafts/format/

Target: 11.4

With the benefit of 6 months of experimentation in the contrib space:

  • missing handlers for the config entity
  • a mechanism to publish the overriden config entities as CSS (page_top like UI Skins do? A specific route like Design Token modules do?
  • a way for front-dev to predefine a list of scopes?
  • design tokens by SDC (inspired by Design Tokens module)
  • A custom plugin discovery to allow multiple files? Let's try again...

From DCTG, we will cover the extra features:

  • $deprecated
  • Remaining token types
  • the aliases / references system for values
  • a org.drupal key in $extensions to add metadata like labels and initial scopes?
grimreaper’s picture

Assigned: Unassigned » grimreaper
Status: Active » Needs work
pdureau’s picture

DTCG JSON schema:

grimreaper’s picture

Dropping some notes after discussion with @pdureau. Plus personal thinking.

Plugin type PHP, token type
- applicable (either method or in plugin annotation)
- toCssVariable
- fromDtcg
- getFormElement (for Core 11.4)
In DesignToken plugin, value should be an instance of a design tokenValue plugin with value.
Serialization only for config entity.

design_token.group__group__border_radius.yml:

id: 'group__group__border_radius'
type: 'border'
scopes:
  :root:
    value: '5'
    unit: 'px'
  %foo:
    value: '10'
    unit: 'em'

Config schema:

design_token.type.border:
  type: mapping
  ...
design_token.type.font_family:
  type: string
  ...
grimreaper’s picture

DONE:
- design token value plugin types
- some design token values to POC, simple and a composite one
- put parsing logic entirely in design token plugin manager

TODO:

- need to take care of name with spaces.
- config entity
- tests

grimreaper’s picture

DONE:
- minimum config entity
- config schema
- rework design token value plugin data injection
- CSS generation/serialization from config entities

TODO:
- validate tokens.yml against JSON schema

grimreaper’s picture

Pushed WIP JSON:Schema validation.

Put similar mechanism as Icon API and Style API MR, but it validates against invalid/non-existing type. I think it is because of remote schema references. Didn't have time to ensure it is due to that yet.

pdureau’s picture

Thanks a lot for the great work done last days. It is impressive.

Some feedbacks:

JSON schemas

There is something to do with the Json schemas: https://git.drupalcode.org/project/drupal/-/tree/485bc5767e0112afac52c0b...

  • We didn't find an official public URL for them. The files copied here mention https://tokens.unpunny.fun/schemas/ which doesn't look like a legit source.
  • The versioning of those schemas is unclear upstream
  • The mechanism to check according to the schema will be completed in the Drupal 11.4 proposal (the follow-up of the current issue)
  • The way Core is organizing its schema is still unclear, especially considering the naming of the SDC ones (issue to create)

So, I am proposing to remove them for now and add them back in 11.4 proposal.

Discovery

Today, we use Drupal\Core\Plugin\Discovery\YamlDiscovery to look for {provider}.tokens.yml files.

This is good enough but could be improved by:

  • only {provider}.tokens.yml file at root AND any tokens/{whatever}.tokens.yml files
  • any {whatever}.tokens.yml files at root

We know there is more allowed by DTCG specification: any file ending by .tokens.json, or ending with .tokens, but it is out of scope for now (let's not introduce specific discoveries here)

grimreaper’s picture

Thanks for the feedbacks.

JSON schemas: removed

Discovery: I tried something but see review comment in MR, YamlDirectoryDiscovery expects an identifier key which is not present as token identifier is its path.

pdureau’s picture

Here is an explanation of each added/modified files (outside tests).

API

We are targeting 3 personas:

Themers, with a plugin type to declare tokens with YAML discovery:

  • core/config/schema/core.design_token_values.schema.yml
  • core/lib/Drupal/Core/Theme/DesignToken/DesignToken.php
  • core/lib/Drupal/Core/Theme/DesignToken/DesignTokenPluginManager.php
  • core/lib/Drupal/Core/Theme/DesignToken/DesignTokenPluginManagerInterface.php
  • core/lib/Drupal/Core/Theme/DesignToken/DtcgInterface.php
  • core/lib/Drupal/Core/Theme/DesignToken/Group.php

Back developers, with a plugin type to implement the DTCG (the W3C affiliated standard we are covering) logic proper:

  • core/lib/Drupal/Core/Theme/DesignToken/Attribute/DesignTokenValue.php
  • core/lib/Drupal/Core/Theme/DesignToken/DesignTokenValueInterface.php
  • core/lib/Drupal/Core/Theme/DesignToken/DesignTokenValuePluginBase.php
  • core/lib/Drupal/Core/Theme/DesignToken/DesignTokenValuePluginManager.php
  • core/lib/Drupal/Core/Theme/DesignToken/DesignTokenValuePluginManagerInterface.php

Site builders, with an entity type to override tokens values:

  • core/config/schema/core.entity.schema.yml: add core.design_token.*
  • core/lib/Drupal/Core/Theme/DesignToken/Serializer/CssVarsSerializer.php
  • core/lib/Drupal/Core/Theme/Entity/DesignToken.php
  • core/lib/Drupal/Core/Theme/Entity/DesignTokenInterface.php

Usage

5 first DTCG value types plugins:

  • core/lib/Drupal/Core/Theme/Plugin/DesignTokenValue/Dimension.php
  • core/lib/Drupal/Core/Theme/Plugin/DesignTokenValue/FontFamily.php
  • core/lib/Drupal/Core/Theme/Plugin/DesignTokenValue/FontWeight.php
  • core/lib/Drupal/Core/Theme/Plugin/DesignTokenValue/Number.php
  • core/lib/Drupal/Core/Theme/Plugin/DesignTokenValue/Typography.php

The 8 others will be implemented in contrib the next months and proposed for 11.4

grimreaper’s picture

Assigned: grimreaper » Unassigned
Status: Needs work » Needs review
grimreaper’s picture

Updated MR to fix existing core tests and code quality.

Added an admin permission to the DesignToken entity for easier integration with existing tests like rest and JSON:API tests. And also at least now the entity has an admin permission for later APIs.

PS: remaining test failure seems unrelated.

pdureau’s picture

rikki_iki’s picture

This is looking really nice!
I'm not sure the scope of this first release but my only comments are;

  1. I would have thought em and % would be valid unit types for core? There's actually a whole bunch of other valid unit types but if the goal is to eventually support custom schemas then listing them all probably isn't necessary here.
  2. Pixels for font size isn't good for accessibility, so I'd suggest a separate dimension schema for font size that doesn't include px... if we can discourage poor a11y practices then we should.
grimreaper’s picture

Hello,

Thanks @rikki_iki for your feedbacks.

Same answer for both points.

This API is driven by the Design token specification (currently in draft) https://www.designtokens.org/tr/drafts/format/. For interoperability with design tools, either creating or consuming design token tools.

And for the moment the only allowed units are "px" and "rem" https://www.designtokens.org/tr/drafts/format/#dimension. I was also surprised that for the dimension type only "px" and "rem" units were allowed.

This can be discussed upstream in https://github.com/design-tokens/community-group (where I know drupalers are already interacting :)) to avoid to have a custom Drupal "layer" or specification.

It seems that there is already an issue about that https://github.com/design-tokens/community-group/issues/245

grimreaper’s picture

I have completed the test coverage.

Remaining test failures in the pipeline are unrelated.

larowlan’s picture

Status: Needs review » Needs work

Left some comments. Really excited about this

rikki_iki’s picture

@larowlan I had the same questions re lack of units, see response in #23. It's going to be a bit limiting until then...

larowlan’s picture

thanks @rikki_iki, shows I didn't read the comments here, just went straight to the MR from the issue summary. Bring on gitlab issues!

kim.pepper’s picture

From the discussion linked above https://github.com/design-tokens/community-group/issues/245

I think a similar approach would be a better way to address prototype/pre-spec implementations. The spec can allow parsers/transformers to ignore invalid tokens. If a vendor wants to add a unit that isn't in the spec yet, they just use it in the unit property as if it was valid and then end users can use a plugin or custom transform (perhaps written by the vendor) in the parser to handle it.

So just because it's not in the spec, doesn't mean we can't support the units we want in our implementation.

grimreaper’s picture

Assigned: Unassigned » grimreaper
pdureau’s picture

So just because it's not in the spec, doesn't mean we can't support the units we want in our implementation.

Yes, we will be able to extend it if we want, this is not blocking.

However:

  • Let's stick with the upstream specs for the 11.3 MR. We are already bringing a lot of value with this scope, and we need to play safe.
  • For 11.4 and later, let's be careful when extending standards with Drupalisms
kim.pepper’s picture

I would prefer a way for users to add support for additional units rather than hard coding to what is in the spec.

grimreaper’s picture

Assigned: grimreaper » Unassigned
Status: Needs work » Needs review

Hi,

Review comments had been addressed and/or waiting for reply.

Back to needs review.

pdureau’s picture

Assigned: Unassigned » larowlan
kim.pepper’s picture

My comment from #30 wasn't responded to:

I would prefer a way for users to add support for additional units rather than hard coding to what is in the spec.

pdureau’s picture

I would prefer a way for users to add support for additional units rather than hard coding to what is in the spec.

Let's play safe, avoid any drupalism, and follow the upstream specs for the 11.3 MR.

However, thanks to the plugin system, Drupal projects may be able override the plugin, adding their own units:

use Drupal\Core\Theme\Plugin\DesignTokenValue\Dimension as CoreDimension;

#[DesignTokenValue(
  id: 'dimension',
  label: new TranslatableMarkup('Dimension'),
)]
class Dimension extends CoreDimension {

  public const array UNIT_TYPES = [
    'px',
    'rem',
   'whatever'
  ];
pdureau’s picture

Assigned: larowlan » Unassigned

Lee is off those days.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new91 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

grimreaper’s picture

Assigned: Unassigned » grimreaper

Hi,

Assigning to myself to fix git conflict.

Currently focusing on #3517033: Add a style utility API before this one.

Also as this MR had not been merged for 11.3, I will:
- add other token types
- handle token references
- reinspect design token spec to see if it has evolved during the last months regarding the concerns of overrides and limited unit types.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

pdureau’s picture

grimreaper’s picture

Assigned: grimreaper » larowlan
Status: Needs work » Needs review
Issue tags: +drupalcampFR2026

Hello,

MR had been rebased.

Validation had been removed.

Initial values in yml files will be validated against the DTCG JSON schema.
Overridden values with config entities will be checked in the config entity form validation.

Re-assigning to @larowlan as he had put the most review comment in Gitlab.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new91 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

grimreaper’s picture

Status: Needs work » Needs review

MR rebased.

grimreaper’s picture

Issue tags: +DevDaysAthens2026
needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new91 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

grimreaper’s picture

Status: Needs work » Needs review

Rebased