Add a CSS variables API

Created on 23 June 2025, about 2 months ago

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 Add a style utility API Active , 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 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. Design tokens which can be accessed through CSS variables are defined in the design system implementation (so, in the Drupal theme). Design tokens live at the page level, but they can be overridden with a CSS selector as a scope. CSS variables naming are solely for use by authors and users; CSS will never give them a meaning beyond what is presented here.

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

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"

Personal opinion:

I am maintainer of this module and I participated to define this format, so I may be biased, but I believe this format is a great starting point. However, there is a few issues we can fix in this proposal:

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 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.

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 variables keyword? We extends our own #variables render property? We add an other universal render property?

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", "runtime tokens", "cascading variables", "scope"

API changes

Only additions.

Data model changes

No.

Feature request
Status

Active

Version

11.0 🔥

Component

theme system

Created by

🇫🇷France pdureau Paris

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @pdureau
  • 🇩🇪Germany D34dMan Hamburg
  • 🇫🇷France pdureau Paris

    Following discussions with @d34dman and @4aficiona2 in 🌱 [Meta] Make Drupal the first "design-system native" CMS + Unify & simplify render & theme systems Active 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:

  • 🇺🇸United States dalemoore

    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

  • 🇫🇷France pdureau Paris

    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, but we can go further, and also do a multiple YAML discovery using decorators to support those 3 filenames but we :

    • {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.

Production build 0.71.5 2024