Skip to content

Editor: Add a background clip block support to facilitate text gradients - #13491

Open
aaronrobertshaw wants to merge 13 commits into
WordPress:trunkfrom
aaronrobertshaw:backport/77141-background-clip-support
Open

aaronrobertshaw wants to merge 13 commits into
WordPress:trunkfrom
aaronrobertshaw:backport/77141-background-clip-support

Conversation

@aaronrobertshaw

@aaronrobertshaw aaronrobertshaw commented Sep 11, 2026 •

Copy link
Copy Markdown

Backport of Gutenberg PRs WordPress/gutenberg#77141 and WordPress/gutenberg#77142

Trac ticket: https://core.trac.wordpress.org/ticket/66094

Adds a background.backgroundClip block support. A block can paint its background into border-box, padding-box, content-box, or the text itself, which gives a gradient text fill.

This PR backports:

  • the backgroundClip definition and value parser in WP_Style_Engine, with a VALID_BACKGROUND_CLIP_VALUES constant,
  • the setting, style and CSS property mapping in WP_Theme_JSON, with the vendor prefixed declarations,
  • background-clip and its vendor prefixed companions in the KSES allowlist,
  • no has-background class when the background is clipped to the text,
  • the escapes a breakpoint or pseudo state needs over an inherited text clip, in WP_Theme_JSON and the block states support,
  • the matching tests.

Box values only reset the fill color. Resetting -webkit-background-clip would discard the clip in Chromium.

Without the escapes, a state over a text clip keeps the gradient in its letters.

The editor controls and the Heading and Paragraph opt-ins arrive with the next Gutenberg package update. Until then, the Paragraph breakpoint tests keep their current expectations, and a test block covers the text fill reset.

Testing instructions

  1. Add this to a block theme's theme.json:
"styles": {
	"blocks": {
		"core/heading": {
			"background": {
				"gradient": "linear-gradient(to right,#ff0000,#0000ff)",
				"backgroundClip": "text"
			},
			"@mobile": {
				"color": {
					"text": "#00aa00"
				}
			}
		},
		"core/group": {
			"background": {
				"gradient": "linear-gradient(to right,#ff0000,#0000ff)",
				"backgroundClip": "content-box"
			}
		}
	}
}
  1. Load a page with a Heading and a Group. The global styles should contain:
:root :where(.wp-block-heading){background-image: linear-gradient(to right,#ff0000,#0000ff);background-clip: text;-webkit-background-clip: text;-webkit-text-fill-color: transparent;}
@media (width <= 480px){:root :where(.wp-block-heading){color: #00aa00;-webkit-text-fill-color: currentColor;}}
:root :where(.wp-block-group){background-image: linear-gradient(to right,#ff0000,#0000ff);background-clip: content-box;-webkit-text-fill-color: currentColor;}
  1. The heading shows the gradient on its letters, and plain green at 480px or narrower.
  2. Give the group padding and a thick dashed border. Its gradient stops at the inner edge of the padding.

Use of AI

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5
Used for: the mechanical port

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

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 props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props aaronrobertshaw, westonruter, isabel_brison, andrewserong, peterwilsoncc.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@aaronrobertshaw

Copy link
Copy Markdown
Author

FYI, things are evolving on the Gutenberg PR this backports. I'll mark this as in draft until that stabilizes.

@aaronrobertshaw
aaronrobertshaw marked this pull request as draft September 20, 2026 08:19
@aaronrobertshaw
aaronrobertshaw force-pushed the backport/77141-background-clip-support branch from 71ac410 to 1295d58 Compare October 1, 2026 07:45
@peterwilsoncc

Copy link
Copy Markdown
Contributor

@aaronrobertshaw Is this ready for review now that WordPress/gutenberg#77141 has merged?

@aaronrobertshaw

Copy link
Copy Markdown
Author

@peterwilsoncc not quite, there is a closely related PR that has changes that might be best backported in this single PR: WordPress/gutenberg#77142.

That Gutenberg PR has a few more changes in the works that might need backporting too. So I'll update as I can and flag this ready once it has the full set of updates.

@aaronrobertshaw

Copy link
Copy Markdown
Author

Both WordPress/gutenberg#77141 and WordPress/gutenberg#77142 have now landed in Gutenberg.

This backport has been updated to cover the latest from both PRs and should be ready for an initial review.

@tellthemachines tellthemachines left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than the style engine class changes this LGTM as it's accurate in comparison with the two source PRs.

Not sure about the warnings on the theme.json class though (might be more helpful if these warnings were consistent between the two repos 😅)

Comment thread src/wp-includes/style-engine/class-wp-style-engine.php
@peterwilsoncc

Copy link
Copy Markdown
Contributor

Not sure about the warnings on the theme.json class though (might be more helpful if these warnings were consistent between the two repos 😅)

Either changing the visibility of the methods to protected or replacing static:: with self:: will work, the phpstan issue because the calls will fail if someone extends WP_Theme_JSON. (Props @westonruter for allowing me to lazyweb this one.)

Comment thread src/wp-includes/class-wp-theme-json.php Outdated
Comment thread src/wp-includes/class-wp-theme-json.php Outdated
@aaronrobertshaw

Copy link
Copy Markdown
Author

Sorry for the delay on this one. I have addressed the feedback and believe this should be good for another review 🙏

@aaronrobertshaw
aaronrobertshaw force-pushed the backport/77141-background-clip-support branch from f095d62 to 210bd28 Compare October 19, 2026 07:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

State handling misses applicable clip styles and feature selectors, while invalid clip values can make text invisible.

5 open issues
What changed in this PR

Backports background clipping support into WordPress’s server-side styling pipeline, enabling text gradients and box clipping.

Changes:

  • Adds clip parsing, theme.json mappings, and allowed CSS properties.
  • Adjusts background classes and state-specific text-fill resets.
  • Adds rendering and stylesheet regression tests.
File Description
tests/​phpunit/​tests/​theme/​wpThemeJson.php Tests responsive and pseudo-state clip handling.
tests/​phpunit/​tests/​style-engine/​styleEngine.php Tests clip declarations and invalid values.
tests/​phpunit/​tests/​block-supports/​wpRenderBackgroundSupport.php Tests clipping output and background classes.
tests/​phpunit/​tests/​block-supports/​states.php Tests state escapes and fill resets.
tests/​phpunit/​tests/​block-supports/​colors.php Tests background classes with clipping.
src/​wp-includes/​style-engine/​class-wp-style-engine.php Adds clip validation and CSS generation.
src/​wp-includes/​kses.php Allows clipping and text-fill properties.
src/​wp-includes/​class-wp-theme-json.php Generates clip styles and state escapes.
src/​wp-includes/​block-supports/​states.php Adds state layering and reset helpers.
src/​wp-includes/​block-supports/​colors.php Suppresses background classes for clipped colors.
src/​wp-includes/​block-supports/​background.php Renders clipping and adjusts background classes.

Review effort: Balanced

Comment thread src/wp-includes/block-supports/states.php Outdated
Comment thread src/wp-includes/block-supports/states.php
Comment thread src/wp-includes/block-supports/states.php
Comment thread src/wp-includes/class-wp-theme-json.php
Comment thread src/wp-includes/class-wp-theme-json.php
Comment thread src/wp-includes/block-supports/colors.php
Comment thread src/wp-includes/class-wp-theme-json.php
Comment thread src/wp-includes/style-engine/class-wp-style-engine.php Outdated
Comment thread src/wp-includes/style-engine/class-wp-style-engine.php Outdated
Comment thread src/wp-includes/block-supports/states.php
Comment thread src/wp-includes/block-supports/states.php
Adds a background.backgroundClip block support, so a block can set which
box its background is painted into: border-box, padding-box, content-box,
or the text itself. Clipping to the text is what gives blocks a gradient
text fill.

Registers the property in the style engine and theme.json, allows the
declarations through KSES, and skips the has-background class when the
background is clipped to the text.

Props aaronrobertshaw.
See #66094.
Backports the PHP from Gutenberg PR 77142. A breakpoint or pseudo state layered over a text clip now unsets the inherited gradient when it sets a box clip, and restores the text fill when it only sets a text color.

See #66094.
…radient

Backports the PHP from Gutenberg PR 77142. A breakpoint or hover text color on a block that supports the background clip now fills the letters again even when the text gradient comes from global styles.

See #66094.
Backports the PHP from Gutenberg PR 77142. A background color on a block that clips its background to the text is clipped to the letters too, so it no longer adds has-background and the padding that comes with it.

See #66094.
…lter_attr().

The 7.2.0 line for background-clip sat between the 7.1.0 line and its continuation, so the 7.1.0 transform and clip-path notes read as 7.2.0 changes. It now continues the existing 7.2.0 entry.

See #66094.
@aaronrobertshaw
aaronrobertshaw force-pushed the backport/77141-background-clip-support branch 2 times, most recently from dc95657 to f103750 Compare October 20, 2026 01:06
@aaronrobertshaw

Copy link
Copy Markdown
Author

Thanks @westonruter, I've addressed your remaining feedback and rebased on the latest trunk.

I think this is ready for a final review. Would you mind taking another look when you get a chance?

@andrewserong andrewserong left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For what it's worth, this is testing well for me and looks like a faithful backport of the already merged Gutenberg PRs, and the addressed feedback appears to have been carefully captured in the open GB PR (WordPress/gutenberg#85057)

Things I tested included:

  • The overall feature and its different values
  • unseting for pseudo and responsive states
  • All seemed to be working nicely as far as I can tell:
Image

Left a tiny nit re: a docblock comment. I was vaguely wondering if it's possible to consolidate any of the wpThemeJson.php tests as they're fairly verbose but not a blocker.

IMO this is in good shape to land for Beta 1, and as mentioned there are ideas for smaller follow-ups that could land during the beta cycle.

LGTM!

* Added support for `dimensions.width` and `dimensions.height`.
* Added support for `typography.textIndent`.
* @since 7.1.0 Added `viewport` property.
* @since 7.2.0 Added `background.backgroundClip` property.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny nit: I think this needs to move a line below, or it looks like background.gradient, dimensions.minWidth etc made it in for 7.2 instead of 7.1

@westonruter westonruter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not super familiar with the style engine code, so I don't want to hold this up.

I will note there remains improvements to static analysis that may land later.


 ------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Line   src/wp-includes/block-supports/colors.php
 ------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  141    Cannot access offset 'background' on mixed.
         🪪  offsetAccess.nonOffsetAccessible
  141    Cannot access offset 'backgroundClip' on mixed.
         🪪  offsetAccess.nonOffsetAccessible
 ------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 ------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Line   src/wp-includes/block-supports/states.php
 ------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  168    Parameter #1 $target of function wp_merge_state_style_objects expects array<string, mixed>, array<mixed, mixed> given.
         🪪  argument.type
  168    Parameter #2 $source of function wp_merge_state_style_objects expects array<string, mixed>, array<mixed, mixed> given.
         🪪  argument.type
  185    Cannot access offset 'gradient' on mixed.
         🪪  offsetAccess.nonOffsetAccessible
  186    Cannot access offset 'backgroundImage' on mixed.
         🪪  offsetAccess.nonOffsetAccessible
  187    Cannot access offset 'gradient' on mixed.
         🪪  offsetAccess.nonOffsetAccessible
  204    Cannot access offset 'backgroundClip' on mixed.
         🪪  offsetAccess.nonOffsetAccessible
  230    Cannot access offset 'backgroundClip' on mixed.
         🪪  offsetAccess.nonOffsetAccessible
  234    Cannot access offset 'text' on mixed.
         🪪  offsetAccess.nonOffsetAccessible
  467    Function wp_add_block_state_style_rule() has no return type specified.
         🪪  missingType.return
  467    Function wp_add_block_state_style_rule() has parameter $css_rules with no value type specified in iterable type array.
         💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
         🪪  missingType.iterableValue
  486    Cannot access offset 'backgroundClip' on mixed.
         🪪  offsetAccess.nonOffsetAccessible
  489    Parameter #1 $state_style of function wp_get_state_text_clip_escape expects array<string, mixed>, array given.
         🪪  argument.type
  490    Parameter #1 $state_style of function wp_get_state_text_fill_reset expects array<string, mixed>, array given.
         🪪  argument.type
  520    Function wp_get_block_state_style_rules() return type has no value type specified in iterable type array.
         💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
         🪪  missingType.iterableValue
  707    Parameter #4 $base_style of function wp_get_block_state_style_rules expects array<string, mixed>|null, array given.
         🪪  argument.type
  728    Parameter #3 $rules_group of function wp_get_block_state_style_rules expects string|null, mixed given.
         🪪  argument.type
  729    Parameter #4 $base_style of function wp_get_block_state_style_rules expects array<string, mixed>|null, array given.
         🪪  argument.type
  795    Parameter #3 $rules_group of function wp_get_block_state_style_rules expects string|null, mixed given.
         🪪  argument.type
  797    Parameter #1 $target of function wp_merge_state_style_objects expects array<string, mixed>, array given.
         🪪  argument.type
  798    Parameter #2 $source of function wp_merge_state_style_objects expects array<string, mixed>, array given.
         🪪  argument.type
 ------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 ------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Line   src/wp-includes/class-wp-theme-json.php
 ------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  1012   Parameter #2 $path of function _wp_array_get expects array, mixed given.
         🪪  argument.type
  1039   Parameter #1 $declarations of static method WP_Theme_JSON::get_state_declarations_with_text_clip_escapes() expects list<array{name: non-falsy-string, value: bool|float|int|string}>, array given.
         💡 array<int|string, mixed> might not be a list.
         🪪  argument.type
  1039   Parameter #2 $state_style of static method WP_Theme_JSON::get_state_declarations_with_text_clip_escapes() expects array<string, mixed>, mixed given.
         🪪  argument.type
  3983   Parameter #2 ...$args of function array_merge expects array, mixed given.
         🪪  argument.type
  3987   Parameter #2 ...$args of function array_merge expects array, mixed given.
         🪪  argument.type
  3991   Parameter #2 ...$args of function array_merge expects array, mixed given.
         🪪  argument.type
  3994   Method WP_Theme_JSON::get_style_state_keys() should return list<non-falsy-string> but returns list.
         🪪  return.type
  3994   Parameter #1 $array of function array_unique expects an array of values castable to string, array given.
         🪪  argument.type
  4009   Method WP_Theme_JSON::get_state_base_styles() return type has no value type specified in iterable type array.
         💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
         🪪  missingType.iterableValue
  4042   Method WP_Theme_JSON::get_state_declarations_with_text_clip_escapes() has parameter $base_styles with no value type specified in iterable type array.
         💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
         🪪  missingType.iterableValue
  4045   Cannot access offset 'backgroundClip' on mixed.
         🪪  offsetAccess.nonOffsetAccessible
  4206   Parameter #1 $declarations of static method WP_Theme_JSON::get_state_declarations_with_text_clip_escapes() expects list<array{name: non-falsy-string, value: bool|float|int|string}>, array given.
         💡 array<int|string, mixed> might not be a list.
         🪪  argument.type
  4207   Parameter #2 $state_style of static method WP_Theme_JSON::get_state_declarations_with_text_clip_escapes() expects array<string, mixed>, array given.
         🪪  argument.type
  4334   Parameter #1 $path of static method WP_Theme_JSON::get_state_base_styles() expects array<string>, array given.
         🪪  argument.type
  4334   Parameter #2 $tree of static method WP_Theme_JSON::get_state_base_styles() expects array<string, mixed>, array given.
         🪪  argument.type
  4334   Parameter #3 $settings of static method WP_Theme_JSON::get_state_base_styles() expects array<string, mixed>, mixed given.
         🪪  argument.type
  4342   Parameter #1 $declarations of static method WP_Theme_JSON::get_state_declarations_with_text_clip_escapes() expects list<array{name: non-falsy-string, value: bool|float|int|string}>, array given.
         💡 array<int|string, mixed> might not be a list.
         🪪  argument.type
  4343   Parameter #2 $state_style of static method WP_Theme_JSON::get_state_declarations_with_text_clip_escapes() expects array<string, mixed>, mixed given.
         🪪  argument.type
  4343   Possibly invalid array key type mixed.
         🪪  offsetAccess.invalidOffset
  4353   Parameter #1 $declarations of static method WP_Theme_JSON::get_state_declarations_with_text_clip_escapes() expects list<array{name: non-falsy-string, value: bool|float|int|string}>, array given.
         💡 array<int|string, mixed> might not be a list.
         🪪  argument.type
  4353   Parameter #2 $state_style of static method WP_Theme_JSON::get_state_declarations_with_text_clip_escapes() expects array<string, mixed>, array given.
         🪪  argument.type
 ------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 ------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Line   src/wp-includes/style-engine/class-wp-style-engine.php
 ------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  798    Method WP_Style_Engine::get_background_clip_css_declarations() has parameter $style_definition with no value type specified in iterable type array.
         💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
         🪪  missingType.iterableValue
 ------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 [ERROR] Found 44 errors on changed lines (base: trunk) 


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants