Make WordPress Core

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.

Generationwp-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;

Conversionwp-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:

  1. 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" }.
  2. In the Site Editor, open Styles → Typography and assign that font to Text or Headings, then save.
  3. 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

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 from theme.json or Global Styles never apply on the front end. The generated custom property kebab-cases the slug (--wp--preset--font-family--n-27, see get_settings_values_by_slug()), but convert_custom_properties() converted var:preset|font-family|n27 references verbatim to var(--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-segment var:preset|<type>|<slug> references using the same _wp_to_kebab_case() as the custom property generation, so both sides always agree. Any other var: 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 new test_get_stylesheet_kebab_cases_preset_reference_slugs fails without the class-wp-theme-json.php change and covers font family, spacing, and duotone references.
  • Manual: register a font family with slug n27 in a theme's theme.json, assign it in Site Editor → Styles → Typography, save, and view the front end: the computed font-family now references the generated --wp--preset--font-family--n-27 custom 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.

#2 @jorgefilipecosta
3 days ago

  • Owner set to jorgefilipecosta
  • Resolutionfixed
  • Status newclosed

In 63641:

Editor: Kebab-case preset slugs when converting references to custom properties.

WP_Theme_JSON kebab-cases a preset slug when generating its custom property but
converted var:preset|type|slug references verbatim, so a slug that changes under
kebab-casing (e.g. n27) referenced a property that was never declared and the
style silently fell back.
Kebab-case the slug segment of three-segment preset references with the same
_wp_to_kebab_case() used to generate them.

Developed in: https://github.com/WordPress/wordpress-develop/pull/12656

Props jorgefilipecosta, ramonopoly, oandregal, fabiankaegy, greenshady, mmaattiiaass.
Fixes #66121.

Note: See TracTickets for help on using tickets.