Opened 3 days ago
Closed 3 days ago
#66121 closed defect (bug) (fixed)
Global Styles: theme.json preset references do not resolve when the preset slug is not already kebab-case
| Reported by: | jorgefilipecosta | Owned by: | jorgefilipecosta |
|---|---|---|---|
| Priority: | normal | Milestone: | Awaiting Review |
| Component: | Editor | Version: | |
| Severity: | normal | Keywords: | has-patch has-unit-tests |
| Cc: | Focuses: |
Description
WP_Theme_JSON kebab-cases a preset slug when it generates that preset's CSS custom property, but not when it converts a stored var:preset|<type>|<slug> reference into a var() usage. For any slug that changes under _wp_to_kebab_case(), the two disagree: the reference names a custom property that was never declared, so the browser finds nothing and the style silently falls back.
Generation — wp-includes/class-wp-theme-json.php:2847, in get_settings_values_by_slug():
$slug = _wp_to_kebab_case( $preset['slug'] );
which declares:
--wp--preset--font-family--n-27: N27, sans-serif;
Conversion — wp-includes/class-wp-theme-json.php:5648, in convert_custom_properties(), replaces | with -- verbatim:
$unwrapped_name = str_replace( $token_in, $token_out, substr( $value, $prefix_len ) ); $value = "var(--wp--$unwrapped_name)";
which emits:
font-family: var(--wp--preset--font-family--n27); /* never declared */
Slugs that are already kebab-case (vivid-red, x-large, 40) coincidentally agree, which is why this only surfaces for slugs such as n27, small2 or blueOrange2.
Steps to reproduce
Without a theme, on trunk:
$theme_json = new WP_Theme_JSON( array( 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( 'typography' => array( 'fontFamilies' => array( array( 'name' => 'N27', 'slug' => 'n27', 'fontFamily' => 'N27, sans-serif' ), ), ), ), 'styles' => array( 'typography' => array( 'fontFamily' => 'var:preset|font-family|n27' ), ), ) ); echo $theme_json->get_stylesheet();
Or through the UI:
- In a block theme's
theme.json, register a font family whose slug changes when kebab-cased:{ "fontFamily": "N27, sans-serif", "name": "N27", "slug": "n27" }. - In the Site Editor, open Styles → Typography and assign that font to Text or Headings, then save.
- View the front end and inspect the generated global stylesheet.
Expected
The reference resolves to the custom property that was generated for the preset, and the font applies:
:root{--wp--preset--font-family--n-27: N27, sans-serif;} body{font-family: var(--wp--preset--font-family--n-27);}
Actual
The declaration and the reference disagree, so the declared value is never used and the element falls back to its inherited font:
:root{--wp--preset--font-family--n-27: N27, sans-serif;} body{font-family: var(--wp--preset--font-family--n27);}
Why only this code path
Every other serializer already kebab-cases, which is why block-level markup and the editor canvas render correctly and only the Global Styles stylesheet is wrong:
Suggested fix
Kebab-case the slug segment of exact three-segment var:preset|<type>|<slug> references in convert_custom_properties(), using the same _wp_to_kebab_case() the generation side uses, so the two can never disagree. Any other var: value converts byte-identically to today.
Environment
Present since WordPress 5.8, the first release to ship WP_Theme_JSON: in 5.8, get_property_value() (class-wp-theme-json.php:943) already converts verbatim, while the same file already kebab-cases the generated custom property. Verified unchanged in 5.9 through 6.3 (where the conversion moved into convert_custom_properties()) and on trunk (7.2-alpha). Not a regression.
Change History (2)
This ticket was mentioned in PR #12656 on WordPress/wordpress-develop by @jorgefilipecosta.
3 days ago
#1
- Keywords has-unit-tests added
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
Backport of https://github.com/WordPress/gutenberg/pull/80583 — syncs
WP_Theme_JSON::convert_custom_properties().Ticket: https://core.trac.wordpress.org/ticket/66121
What
When a preset slug changes under kebab-casing (e.g. a font family with slug
n27), styles referencing that preset fromtheme.jsonor Global Styles never apply on the front end. The generated custom property kebab-cases the slug (--wp--preset--font-family--n-27, seeget_settings_values_by_slug()), butconvert_custom_properties()convertedvar:preset|font-family|n27references verbatim tovar(--wp--preset--font-family--n27), which references a custom property that does not exist. The mismatch affects every preset type referenced this way: colors, gradients, font sizes, font families, spacing, shadows.convert_custom_properties()now kebab-cases the slug segment of exact three-segmentvar:preset|<type>|<slug>references using the same_wp_to_kebab_case()as the custom property generation, so both sides always agree. Any othervar:value converts byte-identically to before.References that render correctly today are unchanged: a reference only works today when its raw slug already equals the kebab-cased declaration name, and
_wp_to_kebab_case()is the identity on exactly those slugs.Testing
vendor/bin/phpunit tests/phpunit/tests/theme/wpThemeJson.php— 234 tests pass. The newtest_get_stylesheet_kebab_cases_preset_reference_slugsfails without theclass-wp-theme-json.phpchange and covers font family, spacing, and duotone references.n27in a theme'stheme.json, assign it in Site Editor → Styles → Typography, save, and view the front end: the computedfont-familynow references the generated--wp--preset--font-family--n-27custom property and the font applies.Trac ticket: TBD
Use of AI Tools
AI assistance: Yes
Used for: drafting the fix, tests, and this description; reviewed and edited by me.