Skip to content

Allow setting viewport tablet and mobile values in theme.json#79104

Open
tellthemachines wants to merge 8 commits into
trunkfrom
try/configurable-breakpoints
Open

Allow setting viewport tablet and mobile values in theme.json#79104
tellthemachines wants to merge 8 commits into
trunkfrom
try/configurable-breakpoints

Conversation

@tellthemachines

@tellthemachines tellthemachines commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

What?

Part of #75707

Adds a viewport object to theme.json settings, with two child values: tablet and mobile. These can be set to px, em or rem values.

This looks like a big changeset but the important part is the additions to the theme json class because that's where the viewport values are processed; the changes in the other files are basically fetching those values from theme.json to replace the hardcoded ones we had before.

Testing Instructions

  1. in your active theme's theme.json, add the following in the settings object:
"viewport": {
			"mobile": "29rem",
			"tablet": "42rem"
		}
  1. Check that styling block viewport states and hiding blocks still works as expected, and media queries are based on theme.json values
  2. Try different values/different units: combinations of different units work as long as the tablet value is greater in size than the mobile one. If it isn't, the tablet media query won't work, though it will still be output. This is a deliberate decision, because I don't think it's core's role to try and fix incorrect input, as long as the output isn't harmful in any way. If the tablet value is smaller than mobile and uses the same unit, the values will be silently replaced with the default ones. I'm undecided whether we should just output a broken media query in this case too, for consistency, but it's pretty easy to check values of the same unit 🤷 .
  3. Check that the device type previews use the specified values and content displays correctly on them.

Use of AI Tools

Used codex/gpt 5.5 to write and iterate on the code. hand reviewed by me.

@tellthemachines tellthemachines self-assigned this Jun 11, 2026
@tellthemachines tellthemachines added [Type] Feature New feature to highlight in changelogs. [Feature] Design Tools Tools that impact the appearance of blocks both to expand the number of tools and improve the experi [Feature] Style States Related to block style states (currently viewport and pseudo-states) labels Jun 11, 2026
@github-actions github-actions Bot added the [Package] Block editor /packages/block-editor label Jun 11, 2026
@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown

Size Change: +1.21 kB (+0.01%)

Total Size: 8.6 MB

📦 View Changed
Filename Size Change
build/modules/lazy-editor/index.min.js 14.4 kB +227 B (+1.6%)
build/scripts/block-editor/index.min.js 381 kB +236 B (+0.06%)
build/scripts/edit-post/index.min.js 53 kB +1 B (0%)
build/scripts/edit-site/index.min.js 297 kB +286 B (+0.1%)
build/scripts/editor/index.min.js 472 kB +455 B (+0.1%)

compressed-size-action

@tellthemachines tellthemachines marked this pull request as draft June 11, 2026 06:47
@tellthemachines

Copy link
Copy Markdown
Contributor Author

note: switched to draft as I didn't intend to mark it ready for review just yet 😅

mostly what's still missing is integration with the device preview, but I mean to test it a bit futher too

@github-actions github-actions Bot added the [Package] Editor /packages/editor label Jun 12, 2026
@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown

Flaky tests detected in 4850ae5.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/27740405987
📝 Reported issues:

@tellthemachines tellthemachines force-pushed the try/configurable-breakpoints branch from 82eee69 to b5beab9 Compare June 15, 2026 05:59
Comment thread lib/class-wp-theme-json-gutenberg.php Outdated
* These are available for all blocks and wrap their styles in the given media query.
* Keep in sync with RESPONSIVE_BREAKPOINTS in packages/global-styles-engine/src/core/render.tsx.
*
* @deprecated Use get_responsive_media_queries() instead.

This comment was marked as resolved.

@tellthemachines tellthemachines force-pushed the try/configurable-breakpoints branch from b5beab9 to 40efcb2 Compare June 15, 2026 07:38
@tellthemachines tellthemachines marked this pull request as ready for review June 15, 2026 07:40
@github-actions

github-actions Bot commented Jun 15, 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.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: tellthemachines <isabel_brison@git.wordpress.org>
Co-authored-by: ramonjd <ramonopoly@git.wordpress.org>
Co-authored-by: talldan <talldanwp@git.wordpress.org>

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

@tellthemachines

Copy link
Copy Markdown
Contributor Author

This PR is now ready for review! It's working pretty well in my testing.

The only oddities I'm coming up against are the legacy responsive controls on certain blocks, which have hardcoded breakpoints that don't always match even the defaults:

  • Columns stack on mobile breaks at 782px (which is our current default tablet breakpoint)
  • Nav overlay on mobile breaks at 600px
  • Media and text stack on mobile breaks at 600px
  • Gallery has a min-width 600px breakpoint

it would be nice if these could all use a configurable breakpoint (but which one? and if we ever allow configuring the amount of breakpoints, how will we pick one to assign here? or will we make that configurable too?)

But I'm happy to just ignore them for now because these controls will eventually be made obsolete by viewport states anyway 😅

@ramonjd ramonjd 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 tested with global and block styles with various values in units in settings.viewport, and it's working pretty well out of the box. Values are hooked up to the device preview dropdown 👍🏻

I got into the weeds a bit and didn't test style variations yet

Kudos for getting this started

Comment thread lib/class-wp-theme-json-gutenberg.php Outdated
* @param array $breakpoints Viewport breakpoint sizes.
* @return bool Whether the breakpoint order is valid.
*/
private static function is_valid_viewport_breakpoint_order( $breakpoints ) {

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.

how does this go with mixed unit breakpoints, e.g., { mobile: "70rem", tablet: "960px" }.

is it possible to have overlapping or impossible media ranges?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes! I left a note about that in point 3 of the testing instructions. If units are different and tablet is smaller than mobile, the tablet query is still output but obv doesn't work. This is easier than doing a whole lot of work to translate between units to check their relative sizes, and imo really it should be the theme author's responsibility to put in values that make sense. What do you think?

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.

Ah apologies, I was in the weeds!

Yeah, agree it should be the author's responsibility.

If the tablet value is smaller than mobile and uses the same unit, the values will be silently replaced with the default ones.

I was gonna say, what if we rejected mixed values, but this sounds like a good compromise. 👍🏻

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah I don't think we should reject mixed values if they work correctly!

};

function getPixelValue( value: string ) {
if ( ! value.endsWith( 'px' ) ) {

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.

Not for this PR, but is there a way to handle block previews in global styles for values are rem or something other than px so that they reflect the current state?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The way responsive previews currently work (both the device preview and the block previews in global styles) is by adding the breakpoint value to the iframe as width. That'll work fine in whichever unit, but the block previews currently have hardcoded values of 480 and 600px for mobile and tablet. We should be able to change that; I'll have a look.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok so I'd forgotten about the scaling that happens further down and expects a unitless px value. That's also what the BlockPreview component expects; unfortunately none of our theme-building UI seems to have taken into account some fairly common theming preferences 😅

Without redoing the logic in BlockPreview, we can get a pretty good approximation of accuracy by converting em and rem assuming a default font size of 16. The fact that the iframe width ends up being set in px will only break things a bit if users have their browser font set to a non-default value, but those users will already be encountering some pretty broken UI elsewhere in the editor, so this is comparatively insignificant.

Anyway, I pushed an update!

Comment thread lib/class-wp-theme-json-gutenberg.php Outdated
* @since 7.1.0
* @var array
*/
const VIEWPORT_BREAKPOINTS = array(

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.

Maybe DEFAULT_VIEWPORT_BREAKPOINTS to make sure?

Comment on lines +46 to +50
$viewport_breakpoints = WP_Theme_JSON_Gutenberg::get_viewport_breakpoints( $viewport_settings );
$viewport_media_queries = array(
'mobile' => "@media (width <= {$viewport_breakpoints['mobile']})",
'tablet' => "@media ({$viewport_breakpoints['mobile']} < width <= {$viewport_breakpoints['tablet']})",
'desktop' => "@media (width > {$viewport_breakpoints['tablet']})",

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.

get_viewport_breakpoints() is public because this output only I guess? which is probably fine.

Unless there was a way to return 'desktop' => "@media (width > {$viewport_breakpoints['tablet']})", from get_responsive_media_queries via options or something?

I have in the back of my mind future extraction of all Theme_JSON styles logic to the style engine methods. Too early to untangle all that now though.

@talldan talldan Jun 16, 2026

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.

Yeah, agree with this, I think it should use get_responsive_media_queries.

The function could have options for defining output, or the other code could omit desktop after getting the options.

I kind of prefer the latter because it means there's no additions to the public API. I guess it'd just be unset( $media_queries[ 'desktop' ] ).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

you mean passing an option to not output desktop, and outputting it by default? you'd still have options as a function param though?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I dunno i think I prefer the adding the option to output desktop. In most cases we won't need it, and it'll be a pain to have to remember to always unset the desktop value 😅

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

uhhh unless #75121 (comment)

$css_rules = array();
$supported_pseudo_states = WP_Theme_JSON_Gutenberg::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ?? array();
$style = $block['attrs']['style'] ?? array();
$css_rules = array();

@talldan talldan Jun 16, 2026

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.

How I hate this = alignment lint rule. 😆

Ok, "hide whitespace" now active for the rest of the review.

Comment thread lib/class-wp-theme-json-gutenberg.php Outdated
Comment on lines +675 to +677
if ( ! static::is_valid_viewport_breakpoint_order( $breakpoints ) ) {
return static::VIEWPORT_BREAKPOINTS;
}

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.

Personally I'm not sure we need to bother checking this. Could we instead leave it up to the author to get it right? Alternatively, the code could output a warning. I'm not sure if there's precedence for theme.json warnings.

How it works now, ignoring the provided values and silently using the default values feels like the wrong choice to me, the code should have some kind of feedback loop about what to fix instead of silence.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeha leaving it up to the author is better I reckon, and we already do it for mixed unit values so might as well do it all the time.

Comment on lines +652 to +659
/**
* Returns viewport breakpoint sizes.
*
* @since 7.1.0
*
* @param array|null $viewport_settings Viewport settings from theme.json.
* @return array Viewport breakpoint sizes.
*/

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.

The docblock is a little bit bare minimum (others in this file too). The code does more than is mentioned which might be useful for others to know.

Comment thread lib/class-wp-theme-json-gutenberg.php Outdated
foreach ( array_keys( static::VIEWPORT_BREAKPOINTS ) as $breakpoint ) {
if (
isset( $viewport_settings[ $breakpoint ] ) &&
static::is_valid_viewport_breakpoint_size( $viewport_settings[ $breakpoint ] )

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.

Probably similar here that it could return a warning, but agree that it's better here to reject a value.

* @param mixed $viewport_settings Viewport settings from theme.json.
* @return array Sanitized viewport settings.
*/
private static function sanitize_viewport_settings( $viewport_settings ) {

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.

This seems to duplicate a lot of what get_viewport_breakpoints is doing. Maybe get_viewport_breakpoints can call sanitize_viewport_settings instead and avoid doing the work itself? The defaults and valid settings then need only be merged.

There's probably a few other ways to structure this as well. I realize that sometimes get_viewport_breakpoints is probably already working on sanitized values.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done!

foreach ( $valid_block_names as $block ) {
$schema_settings_blocks[ $block ] = static::VALID_SETTINGS;
$schema_settings_blocks[ $block ] = static::VALID_SETTINGS;
unset( $schema_settings_blocks[ $block ]['viewport'] );

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.

I guess it doesn't necessarily need improving in this PR, but every block has the same schema by the looks of it, so perhaps it should be possible to generate it only once instead of in the foreach loop.

Comment thread lib/class-wp-theme-json-gutenberg.php Outdated
protected static function remove_insecure_element_styles( $elements, $responsive_media_queries = null ) {
$sanitized = array();
$valid_element_names = array_keys( static::ELEMENTS );
$responsive_media_queries = $responsive_media_queries ?? static::get_responsive_media_queries();

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.

Is there a case where $responsive_media_queries isn't passed in? Maybe the call to static::get_responsive_media_queries() isn't needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't think so as of now. Maybe it's not worth doing this as a hypothetical protection against future uses 😅

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I removed the call and wrapped the bit that processes $responsive_media_queries in a condition to avoid explosions if any extension of the class is calling either of the functions with only one param (they've both been in since 6.8).

Comment thread lib/class-wp-theme-json-gutenberg.php Outdated
$sanitized = array();
protected static function remove_insecure_inner_block_styles( $blocks, $responsive_media_queries = null ) {
$sanitized = array();
$responsive_media_queries = $responsive_media_queries ?? static::get_responsive_media_queries();

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.

Similar here.

Comment on lines +30 to +38
const viewportBreakpoints = getViewportBreakpoints( viewportSettings );
const isMobileViewport = useMediaQuery(
`(width <= ${ viewportBreakpoints.mobile })`,
view
);
const isTabletViewport = useMediaQuery(
`(${ viewportBreakpoints.mobile } < width <= ${ viewportBreakpoints.tablet })`,
view
);

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.

It's a shame this can't use getResponsiveMediaQueries instead, but there's slightly different syntax. This feels quite manual having to assemble the string.

const blockVisibility = attributes?.metadata?.blockVisibility;
const deviceType =
settings?.[ deviceTypeKey ]?.toLowerCase() || 'desktop';
const viewportSettings = settings?.__experimentalFeatures?.viewport;

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.

Feels strange that it has to use __experimentalFeatures all the time. 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

that's apparently the default place to add style-related settings 🤷 I'm not sure how feasible it would be to restructure.

@tellthemachines tellthemachines force-pushed the try/configurable-breakpoints branch from 40efcb2 to c182458 Compare June 18, 2026 02:09
@tellthemachines

Copy link
Copy Markdown
Contributor Author

Thanks for the reviews folks! I think I've addressed all the feedback now.

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

Labels

[Feature] Design Tools Tools that impact the appearance of blocks both to expand the number of tools and improve the experi [Feature] Style States Related to block style states (currently viewport and pseudo-states) [Package] Block editor /packages/block-editor [Package] Editor /packages/editor [Type] Feature New feature to highlight in changelogs.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants