Skip to content

JSON Styles break after filtering #41

Open

Description

@davidallenlewis

Strange issue. Any page with a query loop and filters that has blocks that use filesystem registered JSON block styles (i.e. .is-style-button-outline registered by putting a JSON files in the theme like /styles/blocks/button-outline.json) loses it's styling after filtering.

For example given a button with the following classes:

class="wp-block-button is-style-outline is-button-size-large is-style-outline--10"

The inline CSS before filtering will be:

:root :where(.wp-block-button.is-style-outline--10 .wp-block-button__link) {background-color: transparent;}

The inline CSS after filtering will be:

:root :where(.wp-block-button .wp-block-button__link) {background-color: red;}

The "reload" seems to regenerate inline CSS which reverts to just the defaults in theme.json? Maybe? Not sure. It's confusing.

My button that is NOT inside the Query Loop. And the page has only ONE Query Loop.

The simple workaround is to register Block Styles via JS instead and create all of the SCSS/CSS in the theme's CSS file(s) as opposed to using JSON which gets inlined on load. Would be nice though to be able to use JSON files for styling.

Activity

  1. roborourke commented on Sep 2, 2026

    @roborourke
    Collaborator

    Are you still seeing this on a current WordPress?

    I couldn't reproduce it on 6.8.8 or 7.0.4. Setup: Twenty Twenty-Five, a paragraph outside the query loop carrying a block style variation registered from a theme JSON file in /styles/blocks/ (TT5's own text-display), and one query loop with a taxonomy filter. Filtering through the interactivity router left the variation CSS untouched — same rule, same instance class, same computed value:

    before  block-style-variation-styles-inline-css
            :root :where(p.is-style-text-display--2){font-size: clamp(2.2rem, …);line-height: 1.2;}
            font-size: 47.9968px
    
    after   block-style-variation-styles-inline-css
            :root :where(p.is-style-text-display--2){font-size: clamp(2.2rem, …);line-height: 1.2;}
            font-size: 47.9968px
    

    Given that the symptom you described is the inline CSS regenerating without the variation rules, this looks like it may have been a core-side issue in how those styles were emitted and synced across a router navigation, since fixed upstream.

    If it's still happening for you, the useful details would be your WordPress version, the theme, and the JSON that registers the style — particularly whether the file lives in /styles/ or /styles/blocks/ and whether the affected block is inside or outside the loop. Happy to reopen and dig in with a reproduction. Otherwise I'll close this as fixed upstream in a couple of weeks.


    Generated by Claude Code

  2. davidallenlewis commented on Sep 2, 2026

    @davidallenlewis
    Author

    Still seeing it but the steps to reproduce are more detailed than initially reported. Took awhile to figure out how to reproduce it. Had to go back to my old project repo from May and enable / disable code to narrow it down. Thankfully it wasn't a wild goose chase.

    Environment:

    • WordPress 7.1
    • Current release branch of query-filter (0.4.2)
    • Untouched TT5 Theme
    • Only active plugin is query-filter 0.4.2

    To Reproduce (setup):

    • Create half a dozen test posts with test categories in a clean WP 7.1 install with TT5 theme active
    • Create a Page as follows:
      • Add a Group Block with the TT5 "Style 4" (or whatever) block style applied and dummy text inside
      • Add a Query Loop Block set to "Posts" and 4 Items Per Page
      • Add a Query Filter Block inside the Query Loop Block set to Categories
      • Add a Categories Block inside the Post Template Block with the TT5 "Pill shaped" block style applied

    To Reproduce (steps):

    • Choose a category term from the Query Filter that you know will return fewer posts than the Items Per Page setting
    • The purple background colour from the TT5 "Style 4" on the Group Block will disappear
    • The pill shaped design from the TT5 "Pill shaped" block style on your Categories Block will remain fine

    Observations:

    • Only happens (i.e. Group block loses it's purple background) when result set is less than items per page
    • Removing the "Pill shaped" style on the Categories Block (or the entire Categories block) fixes the issue

    So weird. Here's my stripped down test page markup (below) if that helps. Happy to dig deeper into what's actually happening too but have to switch gears for now. May be able to come back to it tomorrow.

    Thanks so much for looking into this! I'm glad it (seems like) it wasn't a wild goose chase. Although it may still be an upstream issue. But good to know.

    <!-- wp:query {"queryId":0,"query":{"perPage":4,"pages":0,"offset":0,"postType":"post","order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":false,"taxQuery":null,"parents":[],"format":[],"excludeCurrent":null}} -->
    <div class="wp-block-query"><!-- wp:query-filter/taxonomy {"taxonomy":"category","label":"Categories"} /-->
    
    <!-- wp:post-template {"style":{"spacing":{"blockGap":"0"}}} -->
    <!-- wp:post-title {"level":3,"isLink":true,"style":{"spacing":{"margin":{"top":"0","bottom":"0"}},"layout":{"columnSpan":2,"rowSpan":1},"elements":{"link":{"color":{"text":"var:preset|color|contrast"}}}}} /-->
    
    <!-- wp:post-terms {"term":"category","className":"is-style-post-terms-1"} /-->
    <!-- /wp:post-template --></div>
    <!-- /wp:query -->
    
    <!-- wp:group {"metadata":{"name":"Certifications"},"className":"is-style-section-4","style":{"spacing":{"padding":{"top":"var:preset|spacing|gutter","bottom":"var:preset|spacing|gutter"}}},"layout":{"type":"constrained"}} -->
    <div class="wp-block-group is-style-section-4" style="padding-top:var(--wp--preset--spacing--gutter);padding-bottom:var(--wp--preset--spacing--gutter)"><!-- wp:paragraph -->
    <p>Content in a purple box</p>
    <!-- /wp:paragraph --></div>
    <!-- /wp:group -->
  3. roborourke commented on Sep 2, 2026

    @roborourke
    Collaborator

    That repro was the missing piece — thank you for going back to the old project to narrow it. Retracting my "probably fixed upstream" call: it wasn't fixed, my reproduction just didn't satisfy the condition. I had a single variation instance outside the loop and none inside it, which is precisely the case that can't break. Not closing this.

    Your three observations pin the mechanism down, and it's all core-side. Three facts line up:

    1. Variation instance classes are a per-request counter, shared globally. wp_render_block_style_variation_support_styles() in wp-includes/block-supports/block-style-variations.php mints the class as:

    $variation_instance = wp_unique_id( $variation . '--' );
    $class_name         = "is-style-$variation_instance";

    and wp_unique_id() (wp-includes/functions.php:8194) is:

    function wp_unique_id( $prefix = '' ) {
    	static $id_counter = 0;
    	return $prefix . (string) ++$id_counter;
    }

    One static counter for the whole request, shared across every prefix and every caller. So the --N suffix — the --10 and --2 from the earlier reports — is just "how many wp_unique_id() calls happened before this one". It is not stable across requests that render a different set of blocks.

    2. Your Group block is outside the router region. wp-includes/blocks/query.php:33 puts the region on the Query Loop wrapper and nothing else:

    $p->set_attribute( 'data-wp-router-region', 'query-' . $attributes['queryId'] );

    So on a filter navigation the loop's DOM is replaced and everything outside it — your Group — keeps the markup, and the is-style-style-4--N class, minted by the first render.

    3. The router switches the old stylesheet off wholesale. @wordpress/interactivity-router's applyStyles() walks every stylesheet in the document and disables any that isn't in the newly fetched page:

    } else {
    	el.sheet.disabled = true;
    }

    The variation stylesheet's text changes whenever the set of rendered instances changes, so it's never considered "the same node" — the old one gets disabled (older implementations removed it outright; same outcome) and only the new render's version stays live.

    Put together: out-of-region blocks keep classes from the first render, while only the newest render's variation stylesheet is active. Once those two disagree, the rule matches nothing and the styling silently vanishes — which is why the "after" CSS in your original report showed the bare theme.json rule with no instance class at all.

    That accounts for each of your observations:

    • Categories block inside the loop stays fine — its DOM is replaced, so it gets classes minted by the same render that produced the live stylesheet.
    • Only when the result set is smaller than items-per-page — that's when the render emits a different number of variation instances than the initial page did, shifting the counter.
    • Removing the pill style fixes it — with no per-post variation instances, the instance count stops depending on the result count, so the numbering is stable across renders.

    It also explains why your original workaround (registering block styles in JS with plain theme CSS) works: it sidesteps the instance-class machinery entirely. That remains the pragmatic fix for now.

    One thing I couldn't verify

    I wasn't able to run a live reproduction, so there's a gap I'd rather flag than paper over: your Group renders before the loop, and everything that varies renders after it, so on the face of it the Group's own number should be stable — which points at the new stylesheet being incomplete rather than the class being renumbered. Those are two different core bugs with two different upstream reports.

    If you get a moment, the one thing that settles it is, on the same page, before and after filtering:

    1. The Group element's full class attribute.
    2. The full text of <style id="block-style-variation-styles-inline-css">.

    If the class is identical both times and the rule is simply absent from the second stylesheet, it's an emission bug. If the class stays put while the stylesheet defines a different --N, it's the counter desync. Either way I'll write it up as a core ticket against block-style-variations.php and link it here — this isn't something query-filter can fix from the outside, since all it does is trigger a router navigation like any other interactivity-API block.


    Generated by Claude Code

  4. davidallenlewis commented on Sep 8, 2026

    @davidallenlewis
    Author

    I added a group with a block style both before and after the query loop and did some more testing. The styles on the group block before the query loop survive filtering. The styles on the group block after the query loop do not survive filtering. The classnames are the same but it looks like the generated CSS uses a new prefix after filtering for the block after the query loop.

    On Load (4 results in the query loop)

    • Group before Loop is-style-section-4--2 and CSS selector matches
    • Group after Loop is is-style-section-4--7 and CSS selector matches

    After Filtering (2 results in the query loop)

    • Group before Loop is-style-section-4--2 and CSS selector matches
    • Group after Loop is still is-style-section-4--7 but the generated CSS now targets is-style-section-4--5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions