Make WordPress Core

Changeset 62853


Ignore:
Timestamp:
07/27/2026 04:09:33 AM (2 weeks ago)
Author:
ramonopoly
Message:

Theme JSON: Level block-level preset class specificity with :where()

Block-level preset classes are now wrapped in :where() so they keep the same 0-1-0 specificity as root-level preset classes.

A palette defined at block level (via the wp_theme_json_data_theme filter or settings.blocks) emits p.has-x-color at 0-1-1 specificity, while a root-level palette emits .has-x-color at 0-1-0. Responsive style states emit their rules at 0-1-0. Both use !important, so specificity decides the winner: the block-level preset out-ranks the state rule, and a Desktop palette colour overrides the Mobile colour at every viewport width.

Wrapping the block selector in :where() contributes zero specificity while keeping the same scoping, so the preset ties the state rule and loses on source order, matching root-level behaviour. Palette values are unaffected because they flow through the scoped --wp--preset--color--* variable, not the class.

Developed in: https://github.com/WordPress/wordpress-develop/pull/12705(https://github.com/WordPress/wordpress-develop/pull/12705)

Props awetz583, ramonopoly, andrewserong.

Fixes #65724.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-theme-json.php

    r62746 r62853  
    24332433         *   }
    24342434         *
    2435          *   p.has-value-gradient-background {
     2435         *   :where(p).has-value-gradient-background {
    24362436         *     background: value;
    24372437         *   }
     
    26142614         * @since 5.9.0 Added the `$origins` parameter.
    26152615         * @since 6.6.0 Added check for root CSS properties selector.
     2616         * @since 7.1.0 Wraps block-level preset classes in `:where()` to match root-level specificity.
    26162617         *
    26172618         * @param array    $settings Settings to process.
     
    26402641                                        $class_name = static::replace_slug_in_string( $class, $slug );
    26412642
    2642                                         // $selector is often empty, so we can save ourselves the `append_to_selector()` call then.
    2643                                         $new_selector = '' === $selector ? $class_name : static::append_to_selector( $selector, $class_name );
     2643                                        /*
     2644                                         * $selector is often empty (root-level presets), in which case the
     2645                                         * bare class is used. For block-level presets the block selector is
     2646                                         * wrapped in `:where()` so the class keeps the same 0-1-0 specificity
     2647                                         * as a root-level preset. Without this, block-level palette rules
     2648                                         * (e.g. `p.has-x-color`) out-rank equally-important rules that also
     2649                                         * target the same property at 0-1-0, such as per-instance responsive
     2650                                         * state styles.
     2651                                         */
     2652                                        $new_selector = '' === $selector ? $class_name : ':where(' . $selector . ')' . $class_name;
    26442653                                        $stylesheet  .= static::to_ruleset(
    26452654                                                $new_selector,
  • trunk/tests/phpunit/tests/theme/wpThemeJson.php

    r62746 r62853  
    806806         * @ticket 52991
    807807         * @ticket 54336
     808         * @ticket 65724
    808809         */
    809810        public function test_get_stylesheet_preset_classes_work_with_compounded_selectors() {
     
    829830
    830831                $this->assertSame(
    831                         '.wp-block-heading.has-white-color{color: var(--wp--preset--color--white) !important;}.wp-block-heading.has-white-background-color{background-color: var(--wp--preset--color--white) !important;}.wp-block-heading.has-white-border-color{border-color: var(--wp--preset--color--white) !important;}',
     832                        ':where(.wp-block-heading).has-white-color{color: var(--wp--preset--color--white) !important;}:where(.wp-block-heading).has-white-background-color{background-color: var(--wp--preset--color--white) !important;}:where(.wp-block-heading).has-white-border-color{border-color: var(--wp--preset--color--white) !important;}',
    832833                        $theme_json->get_stylesheet( array( 'presets' ) )
    833834                );
     
    895896         * @ticket 60936
    896897         * @ticket 61165
     898         * @ticket 65724
    897899         */
    898900        public function test_get_stylesheet_preset_rules_come_after_block_rules() {
     
    927929
    928930                $styles    = ':root :where(.wp-block-group){color: red;}';
    929                 $presets   = '.wp-block-group.has-grey-color{color: var(--wp--preset--color--grey) !important;}.wp-block-group.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.wp-block-group.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}';
     931                $presets   = ':where(.wp-block-group).has-grey-color{color: var(--wp--preset--color--grey) !important;}:where(.wp-block-group).has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}:where(.wp-block-group).has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}';
    930932                $variables = '.wp-block-group{--wp--preset--color--grey: grey;}';
    931933
Note: See TracChangeset for help on using the changeset viewer.