Description
Description
When creating a dynamic block (registered with a PHP render_callback and no JS save() function), applying useBlockProps() to the top-level element in edit() does not prevent double-wrapping in the block editor.
In this case:
- The block is rendered inside a wrapper in the editor.
- The developer’s own wrapper (the one using useBlockProps()) does not receive the expected .wp-block-[name] class.
- This leads to inconsistency between editor and frontend.
This happens even when:
- edit() returns a single root element
- useBlockProps() is used correctly
- BlockControls and Popover are moved outside the main return
- A save() function is defined as () => null or () => <div {...useBlockProps.save()} />
What’s been tried:
- Moving all controls outside the main wrapper
- Adding a save() function that returns null
- Adding a save() function with useBlockProps.save()
- Avoiding any fragments or sibling return nodes
Impact:
This causes editor-styling bugs when relying on .wp-block-[name] selectors (especially with theme.json). It forces developers to write fragile CSS selectors or override unwanted inner wrapper behavior with layout hacks. It also makes using theme.json to set default styles untenable, as CSS is going to have to be provided for the backend anyway (since the auto-generated styles use a selector that isn't applied)
⸻
Suggested Fix Direction:
Ensure that when:
• useBlockProps() is returned as the top-level node,
• save() is null or present,
• and the structure is otherwise valid,
…the block is not wrapped again in a fallback .wp-block div.
Step-by-step reproduction instructions
- Register a dynamic block using register_block_type() with a PHP render_callback.
- In JS, define a block with an edit() function that returns a single <div {...useBlockProps()}>.
- Do not define a save() function (or define a null-returning one).
- Observe the rendered block in the editor DOM.
Expected:
Actual:
Screenshots, screen recording, code snippet
No response
Environment info
- WordPress 6.8.1, Method theme (custom)
- Chrome 137.0.7151.120, MacOS 15.4.1
- Gutenberg plugin active or not — issue occurs in both
- Block is fully dynamic (render_callback only)
- useBlockProps() applied directly to return root
- Global theme stylesheet (no per-block enqueued styles)
Please confirm that you have searched existing issues in the repo.
- Yes
Please confirm that you have tested with all plugins deactivated except Gutenberg.
- Yes
Please confirm which theme type you used for testing.
- Block
- Classic
- Hybrid (e.g. classic with theme.json)
- Not sure