1

When using InnerBlocks so you can nest parent/child blocks you end up with additional HTML (in the editor) wrapping the child elements like so:

<div class="PARENT">
   <div class="block-editor-inner-blocks">
      <div class="block-editor-block-list__layout">
         <div class="block-editor-block-list__block wp-block is-selected">
            <div class="CHILD"></div>
         </div>
      </div>
   </div>
</div>

This causes a bit of a mess if using things like flexbox or grid on the parent element because it means the styles will instead apply to the .block-editor-inner-blocks instead.

e.g.

.PARENT {
   display: grid;
   grid-template-columns: repeat(3, minmax(0, 33%));
}

To get around this I've found myself having to do hacky overrides or write two different sets of CSS to handle the extra HTML that wraps the blocks in the editor:

.PARENT,
.PARENT .block-editor-block-list__layout {
   display: grid;
   grid-template-columns: repeat(3, minmax(0, 33%));
}
.editor-styles-wrapper .PARENT {
   display: block; /* remove grid in editor */
}

Is there a solution to avoid this? Or are you stuck with two sets of CSS or overrides when viewed inside the editor?

1
  • 2
    there's useBlockProps and useInnerBlockProps that can be used to eliminate the bulk of that markup if not all of it
    – Tom J Nowell
    Jun 17, 2021 at 15:15

2 Answers 2

2

You can eliminate the wrapping containers abandoning InnerBlocks and instead using the useInnerBlocksProps hook which is how the blocks that come with core do it. This will allow your block markup to match the frontend without the editor wrapping things in additional tags.

If we look at the official buttons block:

import {
    BlockControls,
    useBlockProps,
    __experimentalUseInnerBlocksProps as useInnerBlocksProps,
    JustifyContentControl,
} from '@wordpress/block-editor';
...

function ButtonsEdit( {
    attributes: { contentJustification, orientation },
    setAttributes,
} ) {
    const blockProps = useBlockProps( {
        className: classnames( {
            [ `is-content-justification-${ contentJustification }` ]: contentJustification,
            'is-vertical': orientation === 'vertical',
        } ),
    } );
    const innerBlocksProps = useInnerBlocksProps( blockProps, {
        allowedBlocks: ALLOWED_BLOCKS,
        template: BUTTONS_TEMPLATE,
        orientation,
        __experimentalLayout: LAYOUT,
        templateInsertUpdatesSelection: true,
    } );
...
    return (
        <>
...
            <div { ...innerBlocksProps } />
        </>
    );
}

https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/buttons/edit.js

The blocks edit component eliminates the tags that surround the buttons block using useBlockProps, then passes the result to useInnerBlocksProps to do the same for its children. It also passes the parameters that it would have given to the InnerBlocks component as a second parameter.

This way it has full control over the markup of the block while still giving the editor the opportunity to add event handlers and other things.

Not that to use this, you must declare you use the v2 API when registering the block. If you don't then useBlockProps won't work

"apiVersion": 2,

A more minimal example:

import {
    useBlockProps,
    __experimentalUseInnerBlocksProps as useInnerBlocksProps,
} from '@wordpress/block-editor';

const Edit = ( props ) => {
    const blockProps = useBlockProps( {} );
    const innerBlocksProps = useInnerBlocksProps( blockProps, {} );
    return <div { ...innerBlocksProps } />;
};

This also has the benefit of avoiding a common bug in block validation where the innerblocks component shares a tag with another element, and the whitespace generation causes validation to fail, which isn't possible here

14
  • also noting that while the hook is in an experimental namespace, it's been used on all core blocks since WP 5.6 with little to no change in API
    – Tom J Nowell
    Jun 17, 2021 at 15:27
  • Does this only work with JSX as we don't use that to avoid having to transpile the code.
    – Cameron
    Jun 17, 2021 at 15:28
  • Ah it looks like it does. However it's not working with the InnerBlocks and still generates that wrapper HTML: el('div', { ...blockProps }, el(InnerBlocks, { ...innerBlocksProps }) )
    – Cameron
    Jun 17, 2021 at 15:47
  • Those are set like this: const blockProps = useBlockProps( { className: 'staff-list' } ); const innerBlocksProps = useInnerBlocksProps( blockProps, { allowedBlocks: ['staff-list-item-block'] } );
    – Cameron
    Jun 17, 2021 at 15:49
  • Have I missed a trick with the use of it on InnerBlocks? Works fine on the parent element with useBlockProps from the looks of it.
    – Cameron
    Jun 17, 2021 at 15:59
0

Here's one way it can be done without breaking block selection etc:

const blockProps = useBlockProps();
const innerBlocksProps = useInnerBlocksProps();

<div { ...blockProps }>
  { innerBlocksProps.children }
</div>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.