Repository navigation
Responsive @tablet/@mobile styles are ignored in standalone block style variation JSON partials #82392
Description
Description
Gutenberg port of Trac ticket #65992 (milestoned for WordPress 7.1.1), which is being fixed in core via wordpress-develop#13325. The plugin has the identical bug, and since WP_Theme_JSON_Gutenberg overrides core's class while the plugin is active, the core fix alone does not resolve it for Gutenberg users — the plugin needs the equivalent change.
WordPress 7.1 introduced responsive style states using the @tablet and @mobile keys. They work when a block style variation is declared inside theme.json under styles.blocks.<block>.variations.<slug>, but are silently dropped when the same variation is declared in a standalone JSON partial in the theme's styles/ directory. The base styles of the partial are generated correctly; no media-query CSS is generated for the @tablet/@mobile blocks, and nothing surfaces an error in the editor or on the front end.
Root cause (plugin side)
Standalone partials declare their styles at the root of the styles object, and WP_Theme_JSON_Resolver_Gutenberg::get_style_variations_from_directory() sanitizes each partial by running it through WP_Theme_JSON_Gutenberg (lib/class-wp-theme-json-resolver-gutenberg.php:816), so partials are validated against the top-level styles schema in WP_Theme_JSON_Gutenberg::sanitize().
The responsive breakpoint states were only added to the element, block, and block-style-variation branches of the schema (lib/class-wp-theme-json-gutenberg.php:1293, :1320, :1376). The top-level styles schema ($schema['styles'], lib/class-wp-theme-json-gutenberg.php:1402) never gets the breakpoint-state keys, so remove_keys_not_in_schema() strips @tablet and @mobile from partial roots as unknown keys.
The variation branch of the schema cannot cover partials: it validates a different location in the tree, and its guard requires the variation to already be present in $valid_variations, which is derived from the block styles registry — partials are only registered by gutenberg_register_block_style_variations_from_theme_json_partials() after get_style_variations() returns.
The proposed core fix adds the responsive breakpoint states to the top-level styles schema, mirroring what the block style variation branch already does; the plugin fix should do the same in WP_Theme_JSON_Gutenberg::sanitize().
Step-by-step reproduction instructions
- On a block theme with the Gutenberg plugin active, create
styles/article-heading-xl.json:{ "$schema": "https://schemas.wp.org/wp/7.1/theme.json", "version": 3, "title": "Heading XL", "slug": "article-heading-xl", "blockTypes": [ "core/heading", "core/post-title" ], "styles": { "typography": { "fontSize": "40px", "fontWeight": "700", "lineHeight": "1.3" }, "@tablet": { "typography": { "fontSize": "28px" } }, "@mobile": { "typography": { "fontSize": "16px", "lineHeight": "1.35" } } } } - Add a Heading block to a page and apply the "Heading XL" block style.
- View the page on the front end and inspect the heading.
- Resize the viewport below the tablet breakpoint, then below the mobile breakpoint.
Expected: the base rule plus rules wrapped in the tablet and mobile media queries are generated for the variation selector, and the heading font size steps down at each breakpoint.
Actual: only the base rule is generated; the heading keeps the desktop font size at every viewport width. Moving the identical @tablet/@mobile blocks into theme.json under styles.blocks.core/heading.variations.article-heading-xl produces the expected media queries — only the standalone partial path is affected.
Additional context
- Reported on Trac by @imflexwala; root-cause analysis and core patch (with unit tests) by @mitgiselle in wordpress-develop#13325, who offered a companion Gutenberg PR in ticket comment 4 so the two can be reviewed together.
- Screenshots demonstrating the bug and a Playground test report validating the core patch are attached to the Trac ticket.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fields✅ Done
Activity
talldan commentedon Sep 7, 2026
It is already known that the responsive states (
@tablet,@mobile) aren't supported at the rootstyleslevel. The docs mention it (kind of):I think the unexpected consequence is the way it affects the standalone variations, because they get processed as though they're a root theme.json file.
Shame this wasn't caught earlier.
I see the PR #82403. My concern with this is that it'll allow values at the root of a style object in a regular theme.json file too. Although I don't think they are processed, but I think it means they are saved in the database, so could result in those stray values becoming invalid at some point in the future, possibly throwing errors.
Or we decide to support it, but that might be too much for a 7.1.x fix.
talldan commentedon Sep 7, 2026
Ok, after some more investigation, I think I have an idea on how we can fix this, and it's not too difficult. We could add support for the responsive states at the top level like #82403 does, but only when the theme.json data contains a
blockTypesproperty -blockTypesbeing the thing that scopes the styles to a block, essentially making them a style variation.firdaus666 commentedon Sep 9, 2026
I can reproduce a similar issue with pseudo-selector styles in a standalone block style variation JSON file.
Environment:
Example standalone variation:
{ "version": 3, "title": "Theme: Light", "slug": "fir-theme-light", "blockTypes": ["core/button"], "styles": { "color": { "background": "var:preset|color|primary-background", "text": "var:preset|color|tertiary-text" }, ":hover": { "color": { "background": "var:preset|color|secondary-background", "text": "var:preset|color|primary-text" } } } }The base color styles work, but the root
:hoverstyles are removed while the standalone variation is sanitized.I confirmed this by inspecting:
The resolved variation still contains
color, but no longer contains:hover.The same structured
:hoverdeclaration works when the variation is defined inside the maintheme.jsonunder:Using the standalone variation’s
styles.cssfield also works as a temporary workaround:This appears related to the standalone-partial sanitization described here: root state keys are not present in the top-level styles schema and are consequently removed by
remove_keys_not_in_schema().Would this pseudo-selector case belong in this issue, or would you prefer a separate issue linked to it?
talldan commentedon Sep 10, 2026
@firdaus666 The current proposed fix (#82403) should solve the problem with pseudo selectors too.
pbiron commentedon Sep 10, 2026
I just came to open an issue about pseudo-selector styles because I just discovered the problem today.
Environment:
Global Styles: Retain responsive styles in block style variation part…
pbiron commentedon Sep 11, 2026
Can confirm that PR 82403 allows
:hover:, etc to work properly in style variation partials. Thanx everyone!!