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:
- Bootstrap 5: https://getbootstrap.com/docs/5.3/customize/css-variables/#root-variables
- DaisyUI 5: https://daisyui.com/docs/utilities/?lang=en#theme-css-variables
- Shoelace: https://shoelace.style/getting-started/customizing
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:
typeproperty is using the Drupal From API instead of a front-dev friendly format like JSON Schema, W3C's Design Tokens format (DTCG), or CSS value types.- default values are set manually, scope by scope, so any change in the CSS file need to be ported to the YAML file. We may need to use a library like sabberworm/php-css-parser for automatic discovery instead.
- underscores in variable names are automatically replaced by dashes, however underscores are allowed in variable names: https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident
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:
- at theme level, in JSON files, only using the W3C's Design Tokens format (DTCG). JSON is a subset of YAML so it is OK.
- at component level. Example: /with-design-tokens.component.yml
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
jsonrpcmust 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
- https://www.drupal.org/project/css_custom_properties : only a few weeks old, a single commit.
- https://www.drupal.org/project/style_settings: stuck in Drupal 7
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
#themeand 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.
| Comment | File | Size | Author |
|---|
Issue fork drupal-3531854
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
Comment #2
pdureau commentedComment #4
pdureau commentedFollowing 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:
Comment #5
dalemoore commentedI 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
Comment #6
pdureau commentedIf 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:{provider}.tokens.ymlBecause JSON is a subset of YAML (not exactly but it is enough for us), front developers can simply copy paste the content of
.tokens.jsonor.tokensfile 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.
Comment #8
pdureau commentedSo, here we go...
Target: 11.3
Freeze is in 2 weeks (26th October), so let's target a minimal scope addressing the ecosystem :
With:
SerializationInterface)From DCTG, we cover the minimum for working tokens:
See: https://www.designtokens.org/tr/drafts/format/
Target: 11.4
With the benefit of 6 months of experimentation in the contrib space:
page_toplike UI Skins do? A specific route like Design Token modules do?From DCTG, we will cover the extra features:
$deprecatedorg.drupalkey in$extensionsto add metadata like labels and initial scopes?Comment #9
grimreaperComment #10
pdureau commentedDTCG JSON schema:
Comment #11
grimreaperDropping 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:
Config schema:
Comment #12
grimreaperDONE:
- 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
Comment #13
grimreaperDONE:
- 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
Comment #14
grimreaperPushed 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.
Comment #15
pdureau commentedThanks 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...
https://tokens.unpunny.fun/schemas/which doesn't look like a legit source.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\YamlDiscoveryto look for{provider}.tokens.ymlfiles.This is good enough but could be improved by:
{provider}.tokens.ymlfile at root AND anytokens/{whatever}.tokens.ymlfiles{whatever}.tokens.ymlfiles at rootWe 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)Comment #16
grimreaperThanks 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.
Comment #17
pdureau commentedHere 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:
Back developers, with a plugin type to implement the DTCG (the W3C affiliated standard we are covering) logic proper:
Site builders, with an entity type to override tokens values:
core.design_token.*Usage
5 first DTCG value types plugins:
The 8 others will be implemented in contrib the next months and proposed for 11.4
Comment #18
grimreaperComment #19
grimreaperUpdated 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.
Comment #20
pdureau commentedComment #21
rikki_iki commentedThis is looking really nice!
I'm not sure the scope of this first release but my only comments are;
emand%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.px... if we can discourage poor a11y practices then we should.Comment #22
grimreaperHello,
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
Comment #23
grimreaperI have completed the test coverage.
Remaining test failures in the pipeline are unrelated.
Comment #24
larowlanLeft some comments. Really excited about this
Comment #25
rikki_iki commented@larowlan I had the same questions re lack of units, see response in #23. It's going to be a bit limiting until then...
Comment #26
larowlanthanks @rikki_iki, shows I didn't read the comments here, just went straight to the MR from the issue summary. Bring on gitlab issues!
Comment #27
kim.pepperFrom the discussion linked above https://github.com/design-tokens/community-group/issues/245
So just because it's not in the spec, doesn't mean we can't support the units we want in our implementation.
Comment #28
grimreaperComment #29
pdureau commentedYes, we will be able to extend it if we want, this is not blocking.
However:
Comment #30
kim.pepperI would prefer a way for users to add support for additional units rather than hard coding to what is in the spec.
Comment #31
grimreaperHi,
Review comments had been addressed and/or waiting for reply.
Back to needs review.
Comment #32
pdureau commentedComment #33
kim.pepperMy comment from #30 wasn't responded to:
Comment #34
pdureau commentedLet'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:
Comment #35
pdureau commentedLee is off those days.
Comment #36
needs-review-queue-bot commentedThe 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.
Comment #37
grimreaperHi,
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.
Comment #39
pdureau commentedComment #40
grimreaperHello,
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.
Comment #41
needs-review-queue-bot commentedThe 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.
Comment #42
grimreaperMR rebased.
Comment #43
grimreaperComment #44
needs-review-queue-bot commentedThe 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.
Comment #45
grimreaperRebased