Home › Block and Plugin Tutorials › Block Editor › How to use useInnerBlockProps
How to Use useInnerBlocksProps in Nested Blocks
The hook useInnerBlockProps was introduced in WordPress 5.9 to allow developers to control nested blocks.
A React hook is just a function that returns values and only works in functional components. Hooks can help manage state, performing network lookups, or tracking data that may change.
Here’s a quick example of useInnerBlocksProps:
const innerBlocksProps = useInnerBlocksProps(
{
className: 'dlx-list-block__inner',
ref: innerBlocksRef,
},
{
template: [
[ 'dlxplugins/dlx-list-item-block' ],
[ 'dlxplugins/dlx-list-item-block' ],
[ 'dlxplugins/dlx-list-item-block' ],
],
allowedBlocks: [ 'dlxplugins/dlx-list-item-block' ],
templateInsertUpdatesSelection: true,
templateLock: false,
renderAppender: () => ( isSelected ? <ListAppender clientId={ clientId } /> : null )
}
);Code language: JavaScript (javascript)How useInnerBlockProps works
Hook useInnerBlocksProps takes two arguments:
- A JavaScript object with any CSS classes or
refs. - A JavaScript object with InnerBlocks options.
It’s helpful to know what useInnerBlocksProps returns in order to gauge how it works:
export function useInnerBlocksProps( props = {}, options = {} ) {
const fallbackRef = useRef();
const { clientId } = useBlockEditContext();
const ref = props.ref || fallbackRef;
const InnerBlocks =
options.value && options.onChange
? ControlledInnerBlocks
: UncontrolledInnerBlocks;
return {
...props,
ref,
children: (
<InnerBlocks
{ ...options }
clientId={ clientId }
wrapperRef={ ref }
/>
),
};
}Code language: JavaScript (javascript)The hook spreads out any passed props, but returns an object called children, which contains an InnerBlocks component.
Diving into the InnerBlocks component, it returns a block list of child blocks, which suggests this is the main reason why InnerBlocks can only have one parent block.
You can only have one set of InnerBlocks per block
Each block can only have one set of InnerBlocks.
For the sake of this next example, and to ease things in gradually, we’ll use useInnerBlocksProps with just the defaults. With just the defaults, the InnerBlock that will be used is the core/paragraph block.
/**
* Import CSS.
*/
import './editor.scss';
/**
* Import block dependencies.
*/
import { useBlockProps, useInnerBlocksProps, InnerBlocks } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
const QuotesBlockSample = ( props ) => {
// Get the default prop shortcuts.
const { attributes, setAttributes, isSelected, clientId } = props;
// Set block props.
const blockProps = useBlockProps(
{
className: 'dlx-quotes-block',
}
);
// Set InnerBlock props.
const innerBlocksProps = useInnerBlocksProps();
return (
<>
<blockquote { ...blockProps }>
<div { ...innerBlocksProps } />
</blockquote>
</>
);
};
export default QuotesBlockSample;
Code language: JavaScript (javascript)Just as with useBlockProps, you need to use a parent element and spread the return value using spread syntax.
Before we dive into things, I’ll show you a maintenance tip that will save you some time when setting up InnerBlocks.

InnerBlocks and HTML Nesting
The HTML structure is a bit off when setting up the blockquote. The structure is currently as follows:
blockquotediv.block-editor-block-list__layoutpp
This isn’t ideal, as the paragraph tags should be within the blockquote tag and not within any other wrapping element.
In this particular scenario, you can use spread syntax on the blockquote tag itself using useBlockProps and useInnerBlocksProps together.
/* Dependencies */
const QuotesBlockSample = ( props ) => {
// Get the default prop shortcuts.
const { attributes, setAttributes, isSelected, clientId } = props;
// Set block props.
const blockProps = useBlockProps(
{
className: 'dlx-quotes-block',
}
);
// Set InnerBlock props.
const innerBlocksProps = useInnerBlocksProps();
return (
<>
<blockquote { ...blockProps } { ...innerBlocksProps } />
</>
);
};
export default QuotesBlockSample;
Code language: JavaScript (javascript)This results in a more semantic HTML structure without any wrapper DIVs.

Paragraph Tags Wrapped Directly Beneath the Blockquote Tag
Let’s move to some InnerBlocks terminology that will assist us in managing our InnerBlocks.
Allowed blocks
With just the defaults, any block is allowed as an InnerBlock. By passing a list of allowed blocks, you can limit what types of blocks can be inserted. For example, I can set up the blockquote to accept a headline and a paragraph block.
Pass in a parameter called allowedBlocks as part of the 2nd argument to useInnerBlocksProps.
const QuotesBlockSample = ( props ) => {
// Get the default prop shortcuts.
const { attributes, setAttributes, isSelected, clientId } = props;
// Set block props.
const blockProps = useBlockProps(
{
className: 'dlx-quotes-block',
}
);
// Set InnerBlock props.
const innerBlocksProps = useInnerBlocksProps(
{},
{
allowedBlocks: [ 'core/heading', 'core/paragraph' ],
}
);
return (
<>
<blockquote { ...blockProps } { ...innerBlocksProps } />
</>
);
};
export default QuotesBlockSample;
Code language: JavaScript (javascript)This will only allow the paragraph and heading blocks when inserting a new InnerBlock.

Orientation
By default, blocks are a top-down affair. You can change the orientation of how the block behaves in the editor by passing an orientation parameter to useInnerBlocksProps.
Orientation should only be used if you are applying flex or grid styles to the InnerBlocks container and need the inserter to be horizontal instead of vertical. For example, you may have a social media block that has horizontal icons for the social networks.
Here’s an example of using orientation:
const innerBlocksProps = useInnerBlocksProps(
{},
{
allowedBlocks: [ 'core/heading', 'core/paragraph' ],
orientation: 'horizontal',
}
);Code language: JavaScript (javascript)Templates
You can specify a template that will auto-insert when adding the parent block. For example, I can insert a headline followed by a paragraph.
const innerBlocksProps = useInnerBlocksProps(
{},
{
allowedBlocks: ['core/heading', 'core/paragraph'],
template: [
['core/heading', { placeholder: 'Quote Author', level: 3 }],
['core/paragraph', { placeholder: 'Quote Text' }],
]
}
);Code language: JavaScript (javascript)When inserting the block, a headline and paragraph will be auto-inserted for us.

Template lock
You can choose to lock InnerBlocks using the templateLock property. This allows you to control how blocks are handled within the InnerBlocks.
The templateLock property can accept the following values:
contentOnly: This prevents any actions on the InnerBlocks and is fully locked down.all: Similar tocontentOnly, this locks all blocks in place and prevents removing or reordering the blocks.insert: You can pass this to ensure that no new blocks are added, but reordering still works.false: This disables all locking of InnerBlocks, even if the parent block is locked.
Here’s an example with the templateLock set to insert.
const innerBlocksProps = useInnerBlocksProps(
{},
{
allowedBlocks: ['core/heading', 'core/paragraph'],
template: [
['core/heading', { placeholder: 'Quote Author', level: 3 }],
['core/paragraph', { placeholder: 'Quote Text' }],
],
templateLock: 'insert',
}
);Code language: JavaScript (javascript)The result is a locked template, meaning I can’t insert or remove any blocks, but I can reorder them.

Selecting the child block on insert
Using argument templateInsertUpdatesSelection, you can have the child block selected automatically when it’s inserted.
// Set InnerBlock props.
const innerBlocksProps = useInnerBlocksProps(
{
className: 'dlx-list-block__inner',
ref: innerBlocksRef,
},
{
template: [
[ 'dlxplugins/dlx-list-item-block' ],
[ 'dlxplugins/dlx-list-item-block' ],
[ 'dlxplugins/dlx-list-item-block' ],
],
allowedBlocks: [ 'dlxplugins/dlx-list-item-block' ],
templateInsertUpdatesSelection: true,
templateLock: false,
renderAppender: InnerBlocks.DefaultBlockAppender
}
);
Code language: JavaScript (javascript)This is demonstrated in the GIF below.
Selecting the InnerBlocks When a Block is Inserted
Changing the renderAppender
The renderAppender argument allows you to control the rendering of the “+” block button for innerblocks.
const innerBlockProps = useInnerBlocksProps(
{
className: classnames( `dlx-photo-caption-block__inner-blocks dlx-photo-block__caption-inner dlx-photo-block__caption ${ globalStyle }`, {
'has-smart-styles': ( 'advanced' === mode && 'data' !== photoMode && 'featuredImage' !== photoMode && enableSmartStyles ),
} ),
},
{
allowedBlocks: enableAllBlocks ? allBlockNames : photoBlock.captionInnerBlocks,
template: [ [ 'core/paragraph', { align: 'center', placeholder: __( 'Enter your caption here.', 'photo-block' ) } ] ],
templateInsertUpdatesSelection: true,
templateLock: false,
renderAppender: InnerBlocks.DefaultBlockAppender,
}
);
Code language: JavaScript (javascript)Using the default InnerBlocks.DefaultBlockAppender, you can render a regular appender. You would import it in your project like so:
import { InnerBlocks } from '@wordpress/block-editor';Code language: JavaScript (javascript)Creating a custom appender
To create a custom appender, you can create a new component. In this list block example, I’ve created a new appender for the list items.
// eslint-disable-next-line no-unused-vars
import './editor.scss';
import React, { useContext } from 'react';
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import { createBlock } from '@wordpress/blocks';
import { useDispatch } from '@wordpress/data';
import { List } from 'lucide-react';
import { store as blockEditorStore } from '@wordpress/block-editor';
const ListAppender = ( {
clientId,
} ) => {
const { insertBlock } = useDispatch( blockEditorStore );
/**
* Add a list item block to the main block.
*/
const appendBlock = () => {
const newBlocks = createBlock( 'dlxplugins/dlx-list-item-block' );
insertBlock( newBlocks, undefined, clientId );
};
return (
<div className="dlx-list-block__list-appender">
<Button
variant="primary"
className="dlx-list-block__add-list-item"
label={ __( 'Add List Item', 'dlx-list-block' ) }
onClick={ () => {
appendBlock();
} }
icon={ () => <List /> }
>
{ __( 'Add List Item', 'dlx-list-block' ) }
</Button>
</div>
);
};
export default ListAppender;Code language: JavaScript (javascript)With this appender, I can add it to my list block for a bolder appender type.
// Set InnerBlock props.
const innerBlocksProps = useInnerBlocksProps(
{
className: 'dlx-list-block__inner',
ref: innerBlocksRef,
},
{
template: [
[ 'dlxplugins/dlx-list-item-block' ],
[ 'dlxplugins/dlx-list-item-block' ],
[ 'dlxplugins/dlx-list-item-block' ],
],
allowedBlocks: [ 'dlxplugins/dlx-list-item-block' ],
templateInsertUpdatesSelection: true,
templateLock: false,
renderAppender: () => ( isSelected ? <ListAppender clientId={ clientId } /> : null )
}
);
Code language: JavaScript (javascript)Here are the styles I used:
.block-list-appender .dlx-list-block__list-appender {
& > button.components-button {
position: relative;
color: #FFF;
padding: 25px 20px;
svg {
color: #FFF;
}
}
}
.dlx-list-block__inner .block-list-appender.wp-block {
position: relative;
}Code language: SCSS (scss)Here is the result:

Example of a Custom Appender Using InnerBlocks
Conclusion
In this tutorial, we’ve explored the useInnerBlocksProps hook introduced in WordPress 5.9, which enhances our ability to manage inner blocks. We learned about the basic structure and functionality of the hook, including how it accepts properties and options for innerblocks.
We covered essential aspects such as:
- Understanding
useInnerBlocksProps: We analyzed its return values and how it manages innerblocks. - Proper HTML Structure: By using
useInnerBlocksPropswithuseBlockProps, we ensured semantic HTML output without unnecessary wrapping elements. - Allowed Blocks: We discovered how to restrict the types of blocks that can be added as inner blocks using the
allowedBlocksproperty. - Orientation and Templates: We examined how to customize the layout of innerblocks and automatically insert predefined blocks via templates.
- Template Locking: We learned about locking mechanisms for inner blocks to control user interactions, providing a robust editing experience.
- Custom Appenders: We also explored how to create and utilize custom appenders, which provide more control over the user interface and enhance the block insertion process.
If you have any questions, please leave a comment.
Like the tutorial you just read? There's more like it.
There's more where that came from. Enter your email and I'll send you more like it.






