Make WordPress Core

Opened 4 weeks ago

Last modified 4 weeks ago

#65865 new defect (bug)

theme.json settings.viewport: em/rem breakpoints ordered with a fixed 16px assumption but emitted with their original unit, producing an empty media query at larger browser default font sizes

Reported by: courane01 Owned by:
Priority: normal Milestone: Awaiting Review
Component: Editor Version:
Severity: normal Keywords: has-patch has-unit-tests
Cc: Focuses: accessibility

Description

WordPress 7.1 adds settings.viewport (mobile, tablet) to theme.json. These values generate the @mobile / @tablet / @desktop media queries used by responsive block styles and by block viewport visibility (hide-on-mobile/tablet/desktop).

When a breakpoint is given in em or rem, WP_Theme_JSON::get_viewport_breakpoint_value_in_pixels() converts it to pixels using a fixed 16px base — but only for the internal ordering/validation check. The generated media query keeps the original em/rem unit. This is stated in the source:

wp-includes/class-wp-theme-json.php:805  // Use the most common browser default font size as the base
                                         // for em/rem breakpoint order; generated media queries keep
                                         // the original units.
wp-includes/class-wp-theme-json.php:809  return 'px' === $unit ? $number : $number * 16;

In a browser, an em length in a media query resolves against the reader's default font size, not a fixed 16px. So when the two breakpoints in a viewport pair use different units (e.g. mobile in em, tablet in px), the ordering check can pass at a 16px base while the generated range becomes inverted — and therefore empty — at a larger default font size. When the range is empty, the responsive style (or the viewport visibility rule) bound to that breakpoint does not apply, with no admin notice or debug warning.

Steps to reproduce

  1. In a block theme's theme.json, set a mixed-unit pair:
    "settings": { "viewport": { "mobile": "30em", "tablet": "580px" } }
    
  2. WordPress generates @tablet => @media (30em < width <= 580px). Verify:
    wp eval 'var_export( WP_Theme_JSON::get_viewport_media_queries(["mobile"=>"30em","tablet"=>"580px"],["include_desktop"=>true]) );'
    
  3. Load the front end in a browser whose default font size is set to 20px (e.g. Chrome → Appearance → Font size → Large).
  4. At a 20px base, 30em = 600px, so the emitted range is (600px < width <= 580px) — an empty range that matches no viewport width.

Expected

Either the breakpoint pair applies over some range of widths regardless of the reader's default font size, or the author is warned when a mixed-unit pair is accepted whose ordering depends on the 16px assumption — consistent with settings.spacing.spacingScale, which already emits wp_trigger_error() on invalid values.

Actual

At a larger default font size the mixed-unit @tablet range can be empty; any block style or viewport visibility rule bound to that breakpoint silently does not apply. No admin notice, no debug warning.

Impact

Readers who raise their browser's default font size — which includes many low-vision users — can get responsive behavior different from what the theme configured for that reader's font size. In particular, a block set to be hidden on a given viewport can render visible for those readers. The failure is silent for both author and reader. Themes that keep both breakpoints in the same unit are not affected; page-level html { font-size } CSS cannot correct it, because the media-query em is resolved against the browser default, not the document.

Environment

Reproduced on WordPress 7.1-RC3. settings.viewport is new in 7.1 (it does not exist in 7.0.x), so this is not a regression. Confirmed via the generated CSS and via a browser test toggling the default font size (16px → 20px → 24px) with a fixed-pixel control that did not flip. The official 7.1 unit tests (tests/phpunit/tests/theme/wpThemeJson.php) cover same-unit and pixel pairs but include no mixed em+px case.

Follow-up to #65596, which introduced settings.viewport.

Suggested fix (any one)

  • Convert em/rem to px consistently for both the ordering check and the emitted query; or
  • Require both breakpoints in a viewport pair to share a unit; or
  • Emit wp_trigger_error() / _doing_it_wrong() when a mixed-unit pair is accepted whose ordering depends on the 16px assumption, and document the unit constraint in the theme.json reference.

Change History (4)

This ticket was mentioned in PR #13029 on WordPress/wordpress-develop by @khokansardar.


4 weeks ago
#1

  • Keywords has-patch has-unit-tests added

A settings.viewport pair that mixes units, such as "mobile": "30em" with "tablet": "580px", produces an empty @tablet media query for readers who change their browser's default font size, so the responsive styles and visibility rules bound to it silently stop applying.

sanitize_viewport_settings() orders the pair against a fixed 16px base, but the generated query keeps the original units, and a media query resolves em/rem against the reader's default font size instead. The patch removes tablet when it does not share an absolute or relative unit type with mobile, alongside the existing not-larger-than check.

em/rem pairs are kept, since a media query resolves both against the same base; same-unit and default pairs are unaffected.

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

## Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5
Used for: Diagnosing the unit-resolution mismatch, drafting the fix and its unit tests. All changes were reviewed and validated by me.

This ticket was mentioned in Slack in #core by adrianduffell. View the logs.


4 weeks ago

#3 @wildworks
4 weeks ago

Thanks for the report, but what is the difference between this ticket and #65833? Also, as pointed out here, please provide a human-readable summary. The current explanation is excessively verbose and difficult to read.

This ticket was mentioned in Slack in #accessibility by joedolson. View the logs.


4 weeks ago

Note: See TracTickets for help on using tickets.