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.
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>
);
},
} );
Allowed blocks
Using the allowedBlocks
prop, you can further limit, in addition to the allowedBlocks
field in block.json
, which blocks can be inserted as direct descendants of this block. It is useful to determine the list of allowed blocks dynamically, individually for each block. For example, determined by a block attribute:
const { allowedBlocks } = attributes;
//...
<InnerBlocks allowedBlocks={ allowedBlocks } />;
If the list of allowed blocks is always the same, prefer the allowedBlocks
block setting instead.
Orientation
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.
Default block
By default InnerBlocks
opens a list of permitted blocks via allowedBlocks
when the block appender is clicked. You can modify the default block and its attributes that are inserted when the initial block appender is clicked by using the defaultBlock
property. For example:
<InnerBlocks defaultBlock={['core/paragraph', {placeholder: "Lorem ipsum..."}]} directInsert />
By default this behavior is disabled until the directInsert
prop is set to true
. This allows you to specify conditions for when the default block should or should not be inserted.
Template
Use the template property to define a set of blocks that prefill the InnerBlocks component when it has no existing content.. 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.
const MY_TEMPLATE = [
[ 'core/image', {} ],
[ 'core/heading', { placeholder: 'Book Title' } ],
[ 'core/paragraph', { placeholder: 'Summary' } ],
];
//...
edit: () => {
return (
<InnerBlocks
template={ MY_TEMPLATE }
templateLock="all"
/>
);
},
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.
Post template
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' )
);
} );
Using parent, ancestor and children relationships in blocks
A common pattern for using InnerBlocks is to create a custom block that will 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. There are three relationships that builders can use: parent
, ancestor
and allowedBlocks
. 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. - If you assign the
allowedBlocks
then you’re stating a relationship in the opposite direction, i.e., which blocks can be used and inserted as direct descendants of this block.
The key difference between parent
and ancestor
is parent
has finer specificity, while an ancestor
has greater flexibility in its nested hierarchy.
Defining parent block relationship
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" ],
// ...
}
Defining an ancestor block relationship
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" ],
// ...
}
Defining a children block relationship
An example of this is the Navigation block, which is assigned the allowedBlocks
block setting. This makes only a certain subset of block types to be available as direct descendants of the Navigation block. See Navigation code for reference.
The allowedBlocks
setting can be extended by builders of custom blocks. The custom block can hook into the blocks.registerBlockType
filter and add itself to the available children of the Navigation.
When defining a set of possible descendant blocks, use the allowedBlocks
block setting. This limits what blocks are showing in the inserter when inserting a new child block.
{
"title": "Navigation",
"name": "core/navigation",
"allowedBlocks": [ "core/navigation-link", "core/search", "core/social-links", "core/page-list", "core/spacer" ],
// ...
}
Using a React hook
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.
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>
);
},
} );
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.
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} />
);
},
} );
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.
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>
);
},
// ...
} );
<div>
<!-- Inner Blocks get inserted here -->
<!-- The custom html gets rendered on the same level -->
</div>