Browser: Chrome 150.0.7871.125
OS: macOS
WP: 7.0.2
PHP: 8.3.29
URL: localhost
Discovered while building w/Novamira Pro. Summary from Claude
Summary
When a dynamic tag (e.g. {post_terms_some_taxonomy}, {acf_some_field}, or any other provider’s tag) is used inside an element’s custom Attributes panel setting on an element living inside a Query Loop, the tag always resolves against the outer/template post — never against the current loop item. Every element in the loop ends up with the same (often blank) attribute value, regardless of which post is actually being rendered.
This is inconsistent with the same dynamic tags used in ordinary element content (headings, text, etc.), which correctly resolve per loop item.
Steps to reproduce
-
Create a Query Loop of any post type with a taxonomy or custom field that varies per post.
-
Inside the loop template, add a text element whose content uses a dynamic tag for that field (e.g.
{post_terms_your_taxonomy:plain}) — confirm it renders correctly per post. -
On another element in the same loop item, open the Attributes panel and add a custom attribute (e.g.
data-my-value) using the same dynamic tag. -
Preview/publish and inspect the rendered HTML across multiple loop items.
Expected: each loop item’s attribute reflects that post’s actual value, matching the text element from step 2.
Actual: the attribute renders the same value (usually empty) on every loop item, never tracking the loop.
Root cause
File: wp-content/themes/bricks/includes/elements/base.php, class Bricks\Element.
get_custom_attributes() (~line 2739) resolves its value without any loop-context check:
$attribute_value = isset( $field['value'] ) ? bricks_render_dynamic_data( $field['value'], $this->post_id ) : '';
bricks_render_dynamic_data() is a tag-agnostic wrapper around Providers::render_content() — no special-casing per provider. It simply resolves against whatever $post_id it’s given, and here that’s always the element’s own/template post, never the current loop item.
Compare with the correct pattern already used elsewhere in the same class, render_dynamic_data_tag() (~line 4249):
if ( ! $post_id ) {
$post_id = Query::is_looping() && Query::get_loop_object_type() == 'post' ? Query::get_loop_object_id() : $this->post_id;
}
This correctly falls back to Query::get_loop_object_id() when inside a loop. get_custom_attributes() is missing this same check.
Scope
Not ACF-specific — bricks_render_dynamic_data()/Providers::render_content() is tag-source-agnostic, so this affects any dynamic tag (WP core, ACF, Metabox, Pods, WooCommerce, JetEngine, etc.) used in a custom attribute inside any Query Loop. Any site relying on dynamic tags in _attributes for loop-aware data attributes (JS hooks, conditional styling, tracking, etc.) is affected.
Suggested fix
$post_id = Query::is_looping() && Query::get_loop_object_type() == 'post' ? Query::get_loop_object_id() : $this->post_id;
$attribute_value = isset( $field['value'] ) ? bricks_render_dynamic_data( $field['value'], $post_id ) : '';
Current workaround
Deployed a narrow mu-plugin hooking bricks/dynamic_data/render_tag to override the post ID for our specific tag when Query::is_looping() is true. The real fix belongs in get_custom_attributes() so it works generally, for any tag.
