Review brief: Design Tokens API and UI Suite¶
Try it yourself
Everything in this brief runs on the demo site: Drupal 11.5.0-beta1 with the module, its adapters, the Media Library pilot and demo content. You need DDEV 1.25 or newer:
git clone https://github.com/d34dman/design_system_api_demo.git
cd design_system_api_demo
ddev start && ddev install-demo # prints a one-time login link
Why this reaches your area¶
The module is built on core's Design Tokens API
(#3531854), as first shipped
in Drupal 12.0.0-beta1 and 11.5.0-beta1. It doesn't have its own token loader: the shared vocabulary is a
{provider}.tokens.yml file in DTCG format, discovered by plugin.manager.design_token.
On top of it, the module adds what it needed and core doesn't have yet: values per
context, references, a layered resolver, and interim color and shadow value types
until #3622909 adds the
remaining DTCG types to core. Some of
that probably belongs in core, and some of it may clash with where the API is going.
That's what we'd like to find out before an alpha.
Questions for you¶
- Value types and the values adapters need. Core 12.0.0-beta1 has dimension,
fontFamily, fontWeight, number and typography;
#3622909 adds the rest of
DTCG's types, starting with color, border and shadow (work in progress by this
project's maintainer). Core's types take strict DTCG values (a dimension is
{value, unit}), but an adapter needsvar(--admin-color-bg)as a value and an author needs{color.action.primary}. Should core's value types accept references andvar(), or should those be resolved before a value reaches the plugin? - References (#3590100).
We need a reference such as
{color.action.primary}to stay a reference until a value is resolved for a context, because its target has different values in light, dark and high contrast. The comments on #3590100 lean toward resolving at plugin discovery. Would keeping references until render time work for core? → §4 step 3, Q15 $extensions. Core drops$extensionsfrom definitions, so we parse the files a second time to read our per-context defaults ($extensions.design_system_api: { mode=dark: …, contrast=more: … }). Could definitions keep$extensions, or is there a better place for context values?- Overrides per selector vs. contexts. Core's
design_tokenconfig entity stores overrides per CSS selector. We keep author values per theme, with page-side contexts (dark, more contrast) delivered as selector-scoped blocks. Should author values becomedesign_tokenentities, with the theme as the selector scope? → Q16, §9 - Who owns a shared vocabulary. Themes choose their own token names today. This project proposes semantic names that every theme fills and contrib codes against. Should those names live in this contrib project, in UI Suite, or eventually in core? → Q4, Q5
What we built¶
- Vocabulary:
design_system_api.tokens.yml, 43 semantic tokens (color.bg.*,color.fg.*,color.border.*,color.action.*,color.status.*,color.focus,space.*,radius.*,font.size.*,font.weight.*,shadow.*,focus.ring.*).$valueis the frontend default.$extensions.design_system_apiholds values formode=dark,contrast=more, both together (mode=dark contrast=more) and the admin role. - Value types (interim):
colorandshadowplugins accept a CSS value or a{reference}, and reject anything that could break out of a declaration (;,{,},<,>,\,/*). For an invalid value they throw aPluginException; core drops such a token, anddrush ds:tokensreports it. - References: only color tokens use references in v0 (the shadow plugin accepts
them, but no shadow uses one). A reference as the value of a token with a core type
(for example a
dimension) is dropped and reported: core's value plugin throws aTypeError(ValueErrorforfontWeight), which core doesn't catch, so the module replaces core's value plugin manager with a subclass that turns these into aPluginException. The plugin IDs are the ones core will use. Once #3622909 lands, the module drops its own plugins, and the vocabulary has to move to core's value shapes too: colors are hex strings and shadows CSS strings today. - Resolver (
src/Resolver/): layers in order author → adapters → contract defaults. The highest layer that sets a token owns it in every context. References are resolved per context, with cycle detection, and every value records where it came from. Only a whole-value reference ({color.focus}) is checked against the token's type; one inside a value (0 0 0 2px {color.focus}) isn't. This is the module's own layer list, not the DTCG Resolver format that §9 names. - Output: one CSS file per theme with
--ds-*custom properties, plus blocks for dark mode, more contrast and both together that hold only the values that differ. Each kind of context has its own sub-layer, in the order base, contrast, mode, mode-contrast.
Component tokens use the same file format. The Media Library pilot declares
media-card.border with $value: '{color.border.default}': the reference
is the suggested mapping, and a site's author can override it.
See it¶
ddev drush ds:tokens --theme=olivero
ddev drush ds:resolve media-card.border --theme=olivero --mode=dark
--ds-media-card-border: #3f3f46
theme=olivero (frontend), context=mode=dark
• media-card.border = {color.border.default} ← default:frontend [base]
└ color.border.default = #3f3f46 ← default:frontend [mode=dark]
Known issues¶
Found in our own check of the code. Items marked Fixed name the commit that fixed them. We track the open ones ourselves; if one matters for your review, say so in this brief's issue.
- Fixed (
191fe37): a reference in a core-typed token (dimension,number,fontWeight) is now dropped and reported (drush ds:tokens) instead of breaking token discovery for the whole site. Core issue: #3628095. - Embedded references aren't type-checked, and the
shadowtype has no type check, only the check for characters that break out of a declaration. - Partly fixed (
bb2a125): a combined contextmode=dark contrast=more, in a last sub-layer, now covers dark and more contrast on one element, nested either way, and OS plus page choices; kernel and browser tests check it and every text and UI contrast pair; since77237c5a[data-ds-mode="light"]region on an OS-dark page with OS more contrast keeps the contrast values. Still open: contract contrast values don't apply to the tokens an adapter maps, which keep the theme's own values in every context. - Partly fixed (
b79f487): cron now deletes old generated CSS files once they are older than core'sstale_file_threshold. Still open: the token CSS isn't a library; it is ahtml_head_linkon every page, written topublic://during a page request. - A changed adapter file isn't part of the CSS cache key, so it shows only after a cache rebuild.
- Author values are files outside config, so config sync and recipes don't carry them.
- Fixed (
b8ffdc0): the Media Library pilot, which removes the CSS of theme libraries that extend Media Library, now setspointer-events: noneon disabled items itself, so the selection limit holds; FunctionalJavascript tests cover it. - §4 and §9 describe the target: v0 has no
@scopeand doesn't use core'sdesign_tokenoverrides.
Read only these sections¶
What we heard¶
Nothing yet.
Edit note (2026-10-03): Corrected the token count, the claim that a reference in a core-typed token is dropped (it throws), which references are type-checked, that no shadow uses a reference, and that dropping the interim plugins also needs a vocabulary migration; noted that the resolver isn't the DTCG Resolver format. Added Known issues. All found in a pre-review check of the code.
Edit note (2026-10-03, later): Updated What we built and Known issues for the fixes in 191fe37 (references in core-typed tokens are dropped), bb2a125 (combined dark + more contrast context and its sub-layer), b79f487 (old CSS files are cleaned up) and b8ffdc0 (selection limit in the pilot). The core issue is now linked (#3628095). Open known issues are now tracked by the maintainers instead of as separate issues.
Edit note (2026-10-18): Updated Known issues for 77237c5 (light region keeps more contrast); the API also ships in 11.5.0-beta1, and the demo site now runs Drupal 11.5.0-beta1 (the module supports ^11.5 || ^12).