Skip to content

ENGINEERING LEADER

WordPress Developer

Speaker & Teacher

Unapologetic Punk

Mitch Canter

  • Threads
  • Instagram
  • Bluesky
  • LinkedIn
  • GitHub
  • YouTube
WordPress

A Field Guide to Responsive Block Styles in WordPress 7.1

Mitch Canter

•

Reading time: 5 minutes
A blue laptop displaying the WordPress logo on a speckled blue surface

WordPress 7.1 added responsive styling to the block editor. Basically, you can now define how a block looks at different screen sizes without writing a line of CSS. It’s one of the longest-standing requests in block-theme land, and it landed alongside a few related pieces: configurable breakpoints, pseudo-state styling, and – importantly – some new controls for managing it’s use and access.

This post is more technical than “hey, it shipped” – we hit the theme.json syntax, the defaults, the constraints, and how it fits with the fluid typography and spacing tokens you may already have in your theme.

Responsive Styling

Responsive styling is available in two places: Global Styles (these are scoped more globally) and individual block instances (more locally scoped – on a block-by-block basis).

There are two default “viewports” set:

@mobile	@media (width <= 480px)
@tablet	@media (480px < width <= 782px)

Notice there’s no @desktop style – that’s because that’s the default base style – any @ reference will override that default.

Theme.JSON Syntax

Block-level (Global) responsive styles nest a viewport key inside the block’s style object:

"styles": {
  "blocks": {
    "core/group": {
      "spacing": {
        "padding": { "top": "3rem", "right": "3rem", "bottom": "3rem", "left": "3rem" }
      },
      "@mobile": {
        "spacing": {
          "padding": { "top": "1rem", "right": "1rem", "bottom": "1rem", "left": "1rem" }
        }
      }
    }
  }
}

That’s 3rem of padding everywhere, dropping to 1rem at the 480px breapoint and below. Notice that the structure in theme.json is the same, just scoped more specifically to the @mobile object. Every instance of that block is affected at all levels in a site.

Instance-level styling is found in the block’s style attribute as you look at the post content:

<!-- wp:paragraph {"style":{"@mobile":{"typography":{"fontSize":"1rem"}}}} -->
<p>Text with a responsive font size.</p>
<!-- /wp:paragraph -->

Two notes, because i know someone’s going to ask about it:

  • Non-layout per-instance declarations are output with !important so they can override inline styles. Yes, it drives me nuts, too.
  • Layout values and blockGap go through the existing layout support rather than that path. Which, is better anyway since we’re separating content with layout anyway – or we should be, at least.

Setting Your Own Breakpoints

A settings.viewport object in the theme.json can let you override the defaults with your own:

"settings": {
  "viewport": { "mobile": "30rem", "tablet": "45rem" }
}

This will generate, in CSS:

@media (width <= 30rem) { /* Mobile */ }
@media (30rem < width <= 45rem) { /* Tablet */ }

One thing to note: you can only use px, em, or rem values here – no %s. The default (782px) matches the breakpoint WordPress already uses natively in its admin screens. If you already have breakpoints set in CSS (using @media queries, most likely) you should match those up to your theme.json objects using the above code: otherwise, you’ll end up with conflicting breakpoints.

pseudo- and custom style states

This is a long-standing feature that I’ve been asking for a long time. While it’s not exactly the same as the responsive styling, it uses the same underlying style-states, so I want to mention it here.

Currently, the supported pseudo states are limited to the usual suspects: :hover, :focus, :focus-visible, and :active. Which, if you’re familiar with these uses, means the next limitation is no surprise – these are only really supported officially on Button and Navigation Link blocks. If you use these states anywhere else, you’ll want to keep using your own CSS for now.

That said, the same syntax to responsive breakpoints applies here as well:

"styles": {
  "blocks": {
    "core/button": {
      "color": { "background": "black", "text": "white" },
      ":hover": {
        "color": { "background": "blue" }
      }
    }
  }
}

…and can live inside of viewports, which brings us full circle:

"@mobile": {
  ":hover": {
    "color": { "background": "var:preset|color|contrast", "text": "var:preset|color|base" }
  }
}

It can also live in the post content, but I’d not recommend that – doing it this way renders it invisible to your design system. Best to keep it tokenized in theme.json. BUT, if you need the override, you can use it:

<!-- wp:button {"backgroundColor":"accent-3","style":{":hover":{"color":{"background":"var:preset|color|accent-2"}}}} -->

One note: Navigation links also support the :current state – which showcases which link you are actively navigated to. This state, plus other custom states, generate a CSS class, and require registration through the block.json‘s selectors property:

":current": {
  "color": { "text": "var:preset|color|contrast" }
}

How can I disable the UI?

Two filters/settings exist, and they behave the same way:

function example_disable_responsive_editing( $settings ) {
  $settings['responsiveEditingEnabled'] = false;
  return $settings;
}
add_filter( 'block_editor_settings_all', 'example_disable_responsive_editing' );
  • responsiveEditingEnabled controls the responsive editing interface.
  • blockStatesEditingEnabled does the same for pseudo-state editing. Defaults to true.

That said, both govern the editing interface only. Any styles saved into theme.json, in Global Styles, or in the style attribute on a block are left alone with this filter. Media query CSS is still also generated. This only removes the UI, not the functionality.

I’m already using token and primitives… how does this fit in?

The main thought here is that the viewport styles change at a threshold, while the fluid tokens can change continuously. Both are responsive, but solve different problems. Something like clamp()responds at 900px – which no default viewport covers. Viewport styles can also control layout changes, like changing a row to a column for organizational purposes.

Presets Become CSS Custom Properties

Everything defined in theme.json presets is emitted as a custom property:

  • --wp--preset--color--primary
  • --wp--preset--font-size--large
  • --wp--custom--*

And so on. This works for anything under the settings.custom object. Those tokens are available to you in your stylesheet, as well – without needing to refine them.

Fluid Typography

Setting "fluid": true in the typography settings, with per-size min and max values, produces font sizes that work between viewport widths. This is the mechanism that handles continuous type scaling; no media query is involved.

Clamp() in the spacing scale

Spacing presets accept clamp(), which does for rhythm what fluid typography does for type – a section gap that compresses smoothly as the viewport narrows.

Available Constraints

These may seem familiar to anyone who’s in theme.json regularly, but these options are also availble to you:

  • settings.color.custom — the custom color picker
  • settings.typography.customFontSize — arbitrary font-size entry
  • settings.spacing.customSpacingSize — arbitrary spacing entry

Set to false, these allow choice from presets rather than arbitrary values. This works well with the “Decisions, Not Options” model that has governed WordPress in the past – and makes for much easier client editing, in my opinion.

Naming Conventions

Preset slugs become the names editors see in the interface and the custom-property names in your CSS. Slugs that describe a role (primary, base, border-light) stay accurate through a palette change; slugs that describe the value (blue-500) don’t.

So, what’s missing?

This is pretty all-encompassing, and we’re recapping a few, but there’s some notable things either missing or coming soon:

  • Pseudo states are limited to Button and Navigation Link.
  • This is limited to two viewports, not an arbitrary number.
  • Breakpoint values are limited to plain lengths. That means no CSS functions or percentages.
  • Container queries are not part of this; these are viewport-width media queries.

Quick Reference and Sources

ThingSyntaxNotes
Mobile styles"@mobile": { ... }≤480px by default
Tablet styles"@tablet": { ... }480–782px by default
Desktop styles(the base style object)No @desktop key exists
Custom breakpointssettings.viewport.mobile / .tabletpx, em, rem only
Pseudo states":hover", ":focus", ":focus-visible", ":active"Button + Nav Link only
Nav Link current item":current"Custom state, class-based
Nesting"@mobile": { ":hover": { ... } }Viewport wraps state
Disable responsive UIresponsiveEditingEnabledUI only; saved styles still render
Disable states UIblockStatesEditingEnabledDefaults to true
WordPress 7.1 responsive styling and style states — quick reference.
  • Responsive block styles and configurable viewports in WordPress 7.1 — Make WordPress Core
  • Pseudo and custom style states in WordPress 7.1 — Make WordPress Core
  • WordPress 7.1 Field Guide — Make WordPress Core

block theme, responsive design, theme.json
  • A Look Back at WordPress

    A Look Back at WordPress

    Reading time: 1 minute

    …we’ve come a long way from this: …to this… …with a brief stop here… …and to where we are today! And how sweet it is! (thanks to Ozh for a wonderful post – if you aren’t familiar with WordPress, head to his site for a history lesson!)

    WordPress
  • WordPress 3.9: An Early Overview

    WordPress 3.9: An Early Overview

    Reading time: 3 minutes

    With the release of WordPress 3.9 beta 1 on March 11, everyone is (or should be) ensuring their plugins and themes are ready for the update.  There’s a lot of exciting things coming from this update, and it’s easy to overlook some of the more technical (but still important) aspects being added into WordPress.  There’s…

    WordPress
  • Home
  • About
  • Speaking
  • Articles
  • Contact