Theme: Add design token fallback generation#75586
Conversation
|
Size Change: 0 B Total Size: 6.84 MB ℹ️ View Unchanged
|
3a2878a to
e6e6356
Compare
e6e6356 to
f342c21
Compare
There was a problem hiding this comment.
These are the fallback values that will be used. We don't expose this file from the package, but we could when it becomes necessary (like if someone wants to write an injection plugin that we don't provide — we'll provide Eslint, Vite, and PostCSS).
| // These foreground tokens sit on a strong brand background. | ||
| // White is the safest fallback regardless of admin theme color. | ||
| '--wpds-color-fg-interactive-brand-strong': '#fff', | ||
| '--wpds-color-fg-interactive-brand-strong-active': '#fff', |
There was a problem hiding this comment.
Because the color-mix strategy can only give us relatively crude approximations of the sophisticated algorithms running in the actual @wordpress/theme, we need to check that we aren't messing up color contrasts too badly. I say "too badly" because non-default schemes aren't really covered by the contrast guarantee, at least as we can see in the current shipping state of WP.
Even so, for contrast safety I think it's better that we hardcode the wpds-color-fg-interactive-brand-strong and wpds-color-fg-interactive-brand-strong-active tokens to #ffffff, to keep contrast as much as possible and better match what's shipping in WP right now.
| // Prefer the WP admin focus width when available. | ||
| '--wpds-border-width-focus': | ||
| 'var(--wp-admin-border-width-focus, 2px)', | ||
| }; |
There was a problem hiding this comment.
To support these responsive widths:
gutenberg/packages/base-styles/_mixins.scss
Lines 511 to 517 in 1c26a4c
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
There was a problem hiding this comment.
Overall, code changes look good, although I wonder if we should add better support for transparency in colors (mostly for futureproof-ability).
Alpha is not modeled for brand tokens (see getOKLCHValues), meaning that the deltaE and color-mix() comparison don't take the alpha channel into account. The conversion of current transparent tokens seems to be happening only because the color-mix() path rejects the match and falls back to the literal hex.
The color fallback logic will not work as intended with semi-transparent colors.
| * NOTE: The regex and replacement logic here mirrors `add-fallback-to-var.ts`. | ||
| * If you update one, update the other to match. |
There was a problem hiding this comment.
This is probably ok given the little maintenance that this code should see over time, but in case, could we remove the need for the manual sync by compiling the .ts version and importing it in the mjs? Or generate the mjs file wrapper automatically?
Not a blocker at all.
There was a problem hiding this comment.
Right, it's not worth the overhead for the current amount of duplication. Could change in the future.
There was a problem hiding this comment.
Nit:
For better testability, we could consider extracting and testing at least optimalMixPercentage with known seed/target pairs, and computeBrandFallback for the three return paths (exact seed match, successful color-mix(), hex fallback when deltaE is too high).
Again, not a blocker.
There was a problem hiding this comment.
I tried this out, but it turned out to be not really worth it in my assessment, because:
- Terrazzo and color.js need to be mocked out because Jest can't load ESM (the mocking lowers confidence).
- The generated fallback values are committed to the repo, so we'll know when any of the values change. At which point, we can check the actual colors in Storybook or whatever.
Mostly the second point, though.
This reverts commit eecfe36.
mirka
left a comment
There was a problem hiding this comment.
@ciampo Good catch about the alpha.
For now, I added a guard in computeBrandFallback that throws if it encounters a hex with an alpha channel. If a semi-transparent brand token is ever added, the build will fail and prompt an explicit decision on our end. I confirmed that it is technically possible to support transparency (rgb(from <color-mix-expr> r g b / <alpha>) to layer alpha on top of the opaque approximation). We could've just supported it here, but I think it would be useful to address that kind of new addition with more scrutiny in a real-life context. As in, we may look at it and decide the fallback would be better as a hardcoded color, for example.
| * NOTE: The regex and replacement logic here mirrors `add-fallback-to-var.ts`. | ||
| * If you update one, update the other to match. |
There was a problem hiding this comment.
Right, it's not worth the overhead for the current amount of duplication. Could change in the future.
There was a problem hiding this comment.
I tried this out, but it turned out to be not really worth it in my assessment, because:
- Terrazzo and color.js need to be mocked out because Jest can't load ESM (the mocking lowers confidence).
- The generated fallback values are committed to the repo, so we'll know when any of the values change. At which point, we can check the actual colors in Storybook or whatever.
Mostly the second point, though.
* Add build-time fallback generation for design system tokens * Add calc() test and cross-reference comments for synced files * Add border-width-focus fallback to --wp-admin-border-width-focus * Hoist primary seed OKLCH/OKLab to module-level constants * Add unit tests for optimalMixPercentage and computeBrandFallback * Throw on brand tokens with alpha channel * Support alpha channel in brand token fallbacks * Revert "Support alpha channel in brand token fallbacks" This reverts commit eecfe36. * Trim tests to alpha guard only * Update fallbacks with new cursor token Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: ciampo <mciampini@git.wordpress.org>
* Add build-time fallback generation for design system tokens * Add calc() test and cross-reference comments for synced files * Add border-width-focus fallback to --wp-admin-border-width-focus * Hoist primary seed OKLCH/OKLab to module-level constants * Add unit tests for optimalMixPercentage and computeBrandFallback * Throw on brand tokens with alpha channel * Support alpha channel in brand token fallbacks * Revert "Support alpha channel in brand token fallbacks" This reverts commit eecfe36. * Trim tests to alpha guard only * Update fallbacks with new cursor token Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: ciampo <mciampini@git.wordpress.org>
|
Manually cherry picked to |
* Add build-time fallback generation for design system tokens * Add calc() test and cross-reference comments for synced files * Add border-width-focus fallback to --wp-admin-border-width-focus * Hoist primary seed OKLCH/OKLab to module-level constants * Add unit tests for optimalMixPercentage and computeBrandFallback * Throw on brand tokens with alpha channel * Support alpha channel in brand token fallbacks * Revert "Support alpha channel in brand token fallbacks" This reverts commit eecfe36. * Trim tests to alpha guard only * Update fallbacks with new cursor token Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: ciampo <mciampini@git.wordpress.org>
* Theme: Add design token fallback generation (#75586) * Add build-time fallback generation for design system tokens * Add calc() test and cross-reference comments for synced files * Add border-width-focus fallback to --wp-admin-border-width-focus * Hoist primary seed OKLCH/OKLab to module-level constants * Add unit tests for optimalMixPercentage and computeBrandFallback * Throw on brand tokens with alpha channel * Support alpha channel in brand token fallbacks * Revert "Support alpha channel in brand token fallbacks" This reverts commit eecfe36. * Trim tests to alpha guard only * Update fallbacks with new cursor token Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: ciampo <mciampini@git.wordpress.org> * Theme: Add build plugins to inject design token fallbacks (#75589) * Add build plugins to inject design token fallbacks * Add changelog * Harden esbuild and Vite token fallback plugins * Fix lint errors in .d.mts type declaration files * Add code comments addressing review feedback * Add token fallback plugin to compileStyles * Use static token map in Stack for build-time fallback injection Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: youknowriad <youknowriad@git.wordpress.org> Co-authored-by: ciampo <mciampini@git.wordpress.org> Co-authored-by: aduth <aduth@git.wordpress.org> # Conflicts: # packages/theme/CHANGELOG.md * Regenerate fallbacks to exclude newer token --------- Co-authored-by: ciampo <mciampini@git.wordpress.org> Co-authored-by: youknowriad <youknowriad@git.wordpress.org> Co-authored-by: aduth <aduth@git.wordpress.org>
What?
Adds infrastructure for generating fallback values for design system tokens (
--wpds-*). This allows components using these tokens to render correctly even without a global design tokens stylesheet.A follow-up PR (#75589) will add the build plugins that inject these fallbacks into CSS and JS files.
Why?
Components built with
@wordpress/uiuse design tokens as CSS custom properties (e.g.var(--wpds-color-fg-default)). The default values for these are provided by a global design tokens stylesheet from@wordpress/theme, which will only be available in WP 7.1+. Without fallback values, the tokens resolve to nothing in older environments, which blocks adoption of@wordpress/uiin plugins that need to support earlier WP versions.By baking in static fallback values at build time,
@wordpress/themebecomes a progressive enhancement — theming kicks in when available, but components still render correctly without it.For brand-derived tokens (the ones influenced by the WP admin theme color), the fallback expressions use
var(--wp-admin-theme-color)andcolor-mix()so the components still respond to the admin color scheme — even without@wordpress/theme.How?
Terrazzo plugin (
terrazzo-plugin-ds-token-fallbacks)A new Terrazzo build plugin generates a
design-token-fallbacks.mjsfile containing a map of every--wpds-*token to its fallback expression. It runs duringnpm run build:tokens.The fallback for each token is determined as follows:
#1e1e1e,4px).color-mix(in oklch, var(--wp-admin-theme-color, <seed>) N%, black/white)expression that approximates the token's value using the admin theme color. If the approximation exceeds a deltaE threshold, falls back to the literal hex.fg-interactive-brand-strong,fg-interactive-brand-strong-active) are pinned to#ffffor contrast safety on strong brand backgrounds.addFallbackToVarfunctionA reusable function that takes a CSS value string and replaces bare
var(--wpds-*)calls withvar(--wpds-*, <fallback>). It's idempotent (skips vars that already have a fallback). This is the core transform that downstream build plugins will use.Two versions:
add-fallback-to-var.ts— generic, takes the fallback map as a parameter (testable in isolation)ds-token-fallbacks.mjs— pre-binds the generated fallback map for convenienceStory
A Storybook story (
brand-fallbacks.story.tsx) that visually demonstrates the brand token fallback rendering for review.Testing Instructions
npm run build:tokensinpackages/theme— verifysrc/prebuilt/js/design-token-fallbacks.mjsis generated.npx jest packages/theme/src/postcss-plugins/test/add-fallback-to-var.test.tsnpm run storybook:dev, navigate to Theme > Color Ramps > Brand Fallbacks).CleanShot.2026-02-17.at.05-45-38.mp4
The color switcher contains preset values that match the standard WP admin schemes. These are the accent colors that are realistically most relevant to us, in that we need to support them reasonably so they aren't considered regressions.