You can create a single block that nests other blocks using the InnerBlocks component. This is used in the Columns block, Social Links block, or any block you want to contain other blocks.
Note: A single block can only contain one InnerBlocks
component.
Here is the basic InnerBlocks usage.
{% codetabs %} {% JSX %}
import { registerBlockType } from '@wordpress/blocks';
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
registerBlockType( 'gutenberg-examples/example-06', {
// ...
edit: () => {
const blockProps = useBlockProps();
return (
<div { ...blockProps }>
<InnerBlocks />
</div>
);
},
save: () => {
const blockProps = useBlockProps.save();
return (
<div { ...blockProps }>
<InnerBlocks.Content />
</div>
);
},
} );
{% Plain %}
( function ( blocks, element, blockEditor ) {
var el = element.createElement;
var InnerBlocks = blockEditor.InnerBlocks;
var useBlockProps = blockEditor.useBlockProps;
blocks.registerBlockType( 'gutenberg-examples/example-06', {
title: 'Example: Inner Blocks',
category: 'design',
edit: function () {
var blockProps = useBlockProps();
return el( 'div', blockProps, el( InnerBlocks ) );
},
save: function () {
var blockProps = useBlockProps.save();
return el( 'div', blockProps, el( InnerBlocks.Content ) );
},
} );
} )( window.wp.blocks, window.wp.element, window.wp.blockEditor );
{% end %}
Using the ALLOWED_BLOCKS
property, you can define the set of blocks allowed in your InnerBlock. This restricts the blocks that can be included only to those listed, all other blocks will not show in the inserter.
const ALLOWED_BLOCKS = [ 'core/image', 'core/paragraph' ];
//...
<InnerBlocks allowedBlocks={ ALLOWED_BLOCKS } />;
By default, InnerBlocks
expects its blocks to be shown in a vertical list. A valid use-case is to style inner blocks to appear horizontally, for instance by adding CSS flex or grid properties to the inner blocks wrapper. When blocks are styled in such a way, the orientation
prop can be set to indicate that a horizontal layout is being used:
<InnerBlocks orientation="horizontal" />
Specifying this prop does not affect the layout of the inner blocks, but results in the block mover icons in the child blocks being displayed horizontally, and also ensures that drag and drop works correctly.
Use the template property to define a set of blocks that prefill the InnerBlocks component when inserted. You can set attributes on the blocks to define their use. The example below shows a book review template using InnerBlocks component and setting placeholders values to show the block usage.
{% codetabs %} {% JSX %}
const MY_TEMPLATE = [
[ 'core/image', {} ],
[ 'core/heading', { placeholder: 'Book Title' } ],
[ 'core/paragraph', { placeholder: 'Summary' } ],
];
//...
edit: () => {
return (
<InnerBlocks
template={ MY_TEMPLATE }
templateLock="all"
/>
);
},
{% Plain %}
const MY_TEMPLATE = [
[ 'core/image', {} ],
[ 'core/heading', { placeholder: 'Book Title' } ],
[ 'core/paragraph', { placeholder: 'Summary' } ],
];
//...
edit: function( props ) {
return el(
InnerBlocks,
{
template: MY_TEMPLATE,
templateLock: "all",
}
);
},
{% end %}
Use the templateLock
property to lock down the template. Using all
locks the template completely so no changes can be made. Using insert
prevents additional blocks from being inserted, but existing blocks can be reordered. See templateLock documentation for additional information.
Unrelated to InnerBlocks
but worth mentioning here, you can create a post template by post type, that preloads the block editor with a set of blocks.
The InnerBlocks
template is for the component in the single block that you created, the rest of the post can include any blocks the user likes. Using a post template, can lock the entire post to just the template you define.
add_action( 'init', function() {
$post_type_object = get_post_type_object( 'post' );
$post_type_object->template = array(
array( 'core/image' ),
array( 'core/heading' )
);
} );
A common pattern for using InnerBlocks is to create a custom block that will be only be available if its parent block is inserted. This allows builders to establish a relationship between blocks, while limiting a nested block's discoverability. Currently, there are two relationships builders can use: parent
and ancestor
. The differences are:
- If you assign a
parent
then you’re stating that the nested block can only be used and inserted as a direct descendant of the parent. - If you assign an
ancestor
then you’re stating that the nested block can only be used and inserted as a descendent of the parent.
The key difference between parent
and ancestor
is parent
has finer specificity, while an ancestor
has greater flexibility in its nested hierarchy.
An example of this is the Column block, which is assigned the parent
block setting. This allows the Column block to only be available as a nested direct descendant in its parent Columns block. Otherwise, the Column block will not be available as an option within the block inserter. See Column code for reference.
When defining a direct descendent block, use the parent
block setting to define which block is the parent. This prevents the nested block from showing in the inserter outside of the InnerBlock it is defined for.
{
"title": "Column",
"name": "core/column",
"parent": [ "core/columns" ],
// ...
}
An example of this is the Comment Author Name block, which is assigned the ancestor
block setting. This allows the Comment Author Name block to only be available as a nested descendant in its ancestral Comment Template block. Otherwise, the Comment Author Name block will not be available as an option within the block inserter. See Comment Author Name code for reference.
The ancestor
relationship allows the Comment Author Name block to be anywhere in the hierarchical tree, and not just a direct child of the parent Comment Template block, while still limiting its availability within the block inserter to only be visible an an option to insert if the Comment Template block is available.
When defining a descendent block, use the ancestor
block setting. This prevents the nested block from showing in the inserter outside of the InnerBlock it is defined for.
{
"title": "Comment Author Name",
"name": "core/comment-author-name",
"ancestor": [ "core/comment-template" ],
// ...
}
You can use a react hook called useInnerBlocksProps
instead of the InnerBlocks
component. This hook allows you to take more control over the markup of inner blocks areas.
The useInnerBlocksProps
is exported from the @wordpress/block-editor
package same as the InnerBlocks
component itself and supports everything the component does. It also works like the useBlockProps
hook.
Here is the basic useInnerBlocksProps
hook usage.
{% codetabs %} {% JSX %}
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
registerBlockType( 'gutenberg-examples/example-06', {
// ...
edit: () => {
const blockProps = useBlockProps();
const innerBlocksProps = useInnerBlocksProps();
return (
<div { ...blockProps }>
<div {...innerBlocksProps} />
</div>
);
},
save: () => {
const blockProps = useBlockProps.save();
const innerBlocksProps = useInnerBlocksProps.save();
return (
<div { ...blockProps }>
<div {...innerBlocksProps} />
</div>
);
},
} );
{% Plain %}
( function ( blocks, element, blockEditor ) {
var el = element.createElement;
var InnerBlocks = blockEditor.InnerBlocks;
var useBlockProps = blockEditor.useBlockProps;
var useInnerBlocksProps = blockEditor.useInnerBlocksProps;
blocks.registerBlockType( 'gutenberg-examples/example-06', {
title: 'Example: Inner Blocks',
category: 'design',
edit: function () {
var blockProps = useBlockProps();
var innerBlocksProps = useInnerBlocksProps();
return el( 'div', blockProps, el( 'div', innerBlocksProps ) );
},
save: function () {
var blockProps = useBlockProps.save();
var innerBlocksProps = useInnerBlocksProps.save();
return el( 'div', blockProps, el( 'div', innerBlocksProps ) );
},
} );
} )( window.wp.blocks, window.wp.element, window.wp.blockEditor );
{% end %}
This hook can also pass objects returned from the useBlockProps
hook to the useInnerBlocksProps
hook. This reduces the number of elements we need to create.
{% codetabs %} {% JSX %}
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
registerBlockType( 'gutenberg-examples/example-06', {
// ...
edit: () => {
const blockProps = useBlockProps();
const innerBlocksProps = useInnerBlocksProps( blockProps );
return (
<div {...innerBlocksProps} />
);
},
save: () => {
const blockProps = useBlockProps.save();
const innerBlocksProps = useInnerBlocksProps.save( blockProps );
return (
<div {...innerBlocksProps} />
);
},
} );
{% Plain %}
( function ( blocks, element, blockEditor ) {
var el = element.createElement;
var InnerBlocks = blockEditor.InnerBlocks;
var useBlockProps = blockEditor.useBlockProps;
var useInnerBlocksProps = blockEditor.useInnerBlocksProps;
blocks.registerBlockType( 'gutenberg-examples/example-06', {
// ...
edit: function () {
var blockProps = useBlockProps();
var innerBlocksProps = useInnerBlocksProps();
return el( 'div', innerBlocksProps );
},
save: function () {
var blockProps = useBlockProps.save();
var innerBlocksProps = useInnerBlocksProps.save();
return el( 'div', innerBlocksProps );
},
} );
} )( window.wp.blocks, window.wp.element, window.wp.blockEditor );
{% end %}
The above code will render to the following markup in the editor:
<div>
<!-- Inner Blocks get inserted here -->
</div>
Another benefit to using the hook approach is using the returned value, which is just an object, and deconstruct to get the react children from the object. This property contains the actual child inner blocks thus we can place elements on the same level as our inner blocks.
{% codetabs %} {% JSX %}
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
registerBlockType( 'gutenberg-examples/example-06', {
// ...
edit: () => {
const blockProps = useBlockProps();
const { children, ...innerBlocksProps } = useInnerBlocksProps( blockProps );
return (
<div {...innerBlocksProps}>
{ children }
<!-- Insert any arbitrary html here at the same level as the children -->
</div>
);
},
// ...
} );
{% Plain %}
( function ( blocks, element, blockEditor ) {
var el = element.createElement;
var InnerBlocks = blockEditor.InnerBlocks;
var useBlockProps = blockEditor.useBlockProps;
var useInnerBlocksProps = blockEditor.useInnerBlocksProps;
blocks.registerBlockType( 'gutenberg-examples/example-06', {
// ...
edit: function () {
var blockProps = useBlockProps();
var { children, ...innerBlocksProps } = useInnerBlocksProps( blockProps );
return el(
'div',
innerBlocksProps,
children,
el(
'div',
{},
'<!-- Insert any arbitrary html here at the same level as the children -->',
)
);
},
// ...
} );
} )( window.wp.blocks, window.wp.element, window.wp.blockEditor );
{% end %}
<div>
<!-- Inner Blocks get inserted here -->
<!-- The custom html gets rendered on the same level -->
</div>