From 40d60c1305a3a5653ce00c10a0e912b818782023 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Thu, 23 Jul 2026 10:58:42 +0100 Subject: [PATCH 1/3] Editor: Kebab-case preset slugs when converting references to custom properties in WP_Theme_JSON. --- src/wp-includes/class-wp-theme-json.php | 23 ++++++--- tests/phpunit/tests/theme/wpThemeJson.php | 60 +++++++++++++++++++++++ 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index f9d07ccaa6c10..ab55898c9271c 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -5565,6 +5565,7 @@ private static function compute_spacing_sizes( $spacing_scale ) { * For example, `var:preset|color|vivid-green-cyan` becomes `var(--wp--preset--color--vivid-green-cyan)`. * * @since 6.3.0 + * @since 7.2.0 Preset reference slugs are kebab-cased to match the generated custom properties. * * @param string $value The variable such as var:preset|color|vivid-green-cyan to convert. * @return string The converted variable. @@ -5575,12 +5576,22 @@ private static function convert_custom_properties( $value ) { $token_in = '|'; $token_out = '--'; if ( str_starts_with( $value, $prefix ) ) { - $unwrapped_name = str_replace( - $token_in, - $token_out, - substr( $value, $prefix_len ) - ); - $value = "var(--wp--$unwrapped_name)"; + $parts = explode( $token_in, substr( $value, $prefix_len ) ); + + /* + * The slug of a preset reference is kebab-cased so the resulting + * custom property matches the one generated from the preset, + * whose slug is also kebab-cased (see `get_settings_values_by_slug()`). + * For slugs that are not already kebab-cased (e.g. `n27`), a verbatim + * conversion produces a reference to a custom property that does + * not exist (`--wp--preset--font-family--n27` instead of the + * generated `--wp--preset--font-family--n-27`). + */ + if ( 3 === count( $parts ) && 'preset' === $parts[0] ) { + $parts[2] = _wp_to_kebab_case( $parts[2] ); + } + + $value = 'var(--wp--' . implode( $token_out, $parts ) . ')'; } return $value; diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index c2cda7bb158d0..2722a18f9fab7 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -984,6 +984,66 @@ public function test_get_stylesheet_generates_proper_classes_and_css_vars_from_s ); } + /** + * References to presets (`var:preset|type|slug`) are converted using the + * same kebab-cased slug as the custom properties generated from the + * presets, so both sides match for slugs that change when kebab-cased. + */ + public function test_get_stylesheet_kebab_cases_preset_reference_slugs() { + $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', + ), + ), + ), + 'spacing' => array( + 'spacingSizes' => array( + array( + 'name' => 'Small 2', + 'slug' => 'small2', + 'size' => '8px', + ), + ), + ), + ), + 'styles' => array( + 'typography' => array( + 'fontFamily' => 'var:preset|font-family|n27', + ), + 'spacing' => array( + 'padding' => array( + 'top' => 'var:preset|spacing|small2', + ), + ), + ), + ) + ); + + $stylesheet = $theme_json->get_stylesheet(); + + // The custom properties generated from the presets kebab-case the slug. + $this->assertStringContainsString( + '--wp--preset--font-family--n-27: N27, sans-serif', + $stylesheet + ); + // References resolve to the same kebab-cased custom property names. + $this->assertStringContainsString( + 'font-family: var(--wp--preset--font-family--n-27)', + $stylesheet + ); + $this->assertStringContainsString( + 'padding-top: var(--wp--preset--spacing--small-2)', + $stylesheet + ); + } + /** * @ticket 56467 * @ticket 58550 From a9544ead512837bc1ee386703658fef0d2acdef1 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 31 Aug 2026 14:33:02 +0100 Subject: [PATCH 2/3] Note the duotone exception in the preset conversion comment --- src/wp-includes/class-wp-theme-json.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index ab55898c9271c..9c0e4765ceecb 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -5586,6 +5586,13 @@ private static function convert_custom_properties( $value ) { * conversion produces a reference to a custom property that does * not exist (`--wp--preset--font-family--n27` instead of the * generated `--wp--preset--font-family--n-27`). + * + * Duotone is the exception: its custom properties are generated by + * `WP_Duotone` from the presets it registers in + * `get_all_global_styles_presets()`. Duotone references are + * kebab-cased all the same: the editor and the JS style engine + * kebab-case the references of every preset type, and + * `WP_Duotone` looks up presets by kebab-cased filter ID. */ if ( 3 === count( $parts ) && 'preset' === $parts[0] ) { $parts[2] = _wp_to_kebab_case( $parts[2] ); From 2f758e401ea08fb3a2242b99bb7fac582824026d Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 31 Aug 2026 14:33:35 +0100 Subject: [PATCH 3/3] Add the duotone preset reference test case --- tests/phpunit/tests/theme/wpThemeJson.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index 2722a18f9fab7..5e0a13cff1757 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -1012,6 +1012,15 @@ public function test_get_stylesheet_kebab_cases_preset_reference_slugs() { ), ), ), + 'color' => array( + 'duotone' => array( + array( + 'colors' => array( '#000000', '#ffffff' ), + 'name' => 'Blue Orange 2', + 'slug' => 'blueOrange2', + ), + ), + ), ), 'styles' => array( 'typography' => array( @@ -1022,6 +1031,13 @@ public function test_get_stylesheet_kebab_cases_preset_reference_slugs() { 'top' => 'var:preset|spacing|small2', ), ), + 'blocks' => array( + 'core/image' => array( + 'filter' => array( + 'duotone' => 'var:preset|duotone|blueOrange2', + ), + ), + ), ), ) ); @@ -1042,6 +1058,10 @@ public function test_get_stylesheet_kebab_cases_preset_reference_slugs() { 'padding-top: var(--wp--preset--spacing--small-2)', $stylesheet ); + $this->assertStringContainsString( + 'var(--wp--preset--duotone--blue-orange-2)', + $stylesheet + ); } /**