Inner Blocks

Inner blocks, or nested blocks, is a component from the @wordpress/block-editor package that allows a parent block to use other child blocks for its structure. These child blocks in the Inner Blocks component can be free form, allowing the child blocks to be rearranged, create multiples of, or locked into a specific pattern.

There is more control over how inner blocks are presented, vs. block patterns, which are basically a suggested template that can be modified. Inner blocks can be locked into a specific pattern, or allow modification, but only with a restricted list of blocks.

A few resources:

To get started, create a new block by running

npm run new-id-block

Within the new block folder, edit.js and save.js are the two folders to work with. Below is sample code from Brian Coords:

The two sections for edit.js are the import call and the edit function:

Code block from edit.js showing the import and edit sections

Within the Edit function, there are a few arguments/settings:

  • allowedBlocks – The core or custom blocks that are allowed to be used within the InnerBlock
  • template – The initial layout for the blocks when there is no content.
  • templateLock – This allows the flexibility for the block. In the example, “all” means that it is fully locked, and the user cannot make modifications to add/remove blocks or move them around. The link shows the other settings for the templateLock.
  • orientation – This one isn’t in the example, but is worth mentioning. This gives the direction (horizontal/vertical) the blocks are laid out. This property controls the orientation of the “move block” arrows that appear on the Block Toolbar for each child block inside the Inner Blocks component.
  • renderAppender – If you want to customize the “Add a block” experience, this is the setting for you. The documentation is sparse, but basically you can display the plus icon, nothing at all, or customize the look:

save.js is more straightforward:

All the save function requires is the InnerBlocks.Content tag, and it will render on the frontend.

Dynamic Blocks: It’s important to note that in a dynamic block with a PHP template you still need to have a save function in Javascript to save the InnerBlocks content. You don’t have to save all of the rest of the markup of your parent block, but the InnerBlocks.Content has to saved so that the markup and block grammar of the child blocks is saved to the post_content.

useInnerBlockProps

With Block API 2, we can have greater control over the markup of the Inner Blocks component in the editor by using the useInnerBlockProps hook. This is the inner blocks version of the useBlockProps hook. See the handbook page on that hook.

In the edit function import the hook from the @wordpress/block-editor package:

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

In your edit function define useInnerBlockProps and pass it an object with any of the parameters you want to output on the HTML element that useInnerBlockProps is spread on. These are optional, but most often you’ll want to use this for modifying the className. Or adding data attributes.

Then on the HTML element use the ...spread syntax to output those parameters on the HTML markup.

export function BlockEdit() {
    const blockProps = useBlockProps();
    const innerBlockProps = useInnerBlocksProps({
        style: {
          color: '#222222',
          backgroundColor: '#eeeeee',
          padding: '15px'
        },
        className: isSelected ? 'additional-class' : '',
        'data-test': 'data attributes'
    });

    return (
        <div {...blockProps}>
            <div {...innerBlockProps} />
        </div>
    )
};

In Save.js you’ll want to do the same as above if using useInnerBlocksProps but you’ll add the .save() method to the end when calling it. Like so:

const innerBlockProps = useInnerBlocksProps.save({
        style: {
          color: '#222222',
          backgroundColor: '#eeeeee',
          padding: '15px'
        },
        className: isSelected ? 'additional-class' : '',
        'data-test': 'data attributes'
    });

See the page on useBlockProps for more details and documentation.

Accessing Data Between Blocks

Child block data from Parent

Accessing the data of the child blocks from the parent via JavaScript is straightforward, via the @wordpress/data module:

const { __ } = wp.i18n;
const { useSelect } = wp.data;
const { store: blockEditorStore } = wp.blockEditor;

/**
 * Block Edit Component
 */
const Edit = (props) => {
	const { clientId } = props;
	const innerBlocks = useSelect(
		(select) => select(blockEditorStore).getBlock(clientId).innerBlocks,
	);

	console.log( innerBlocks ); // This will log all the inner blocks on your browser console
};

For dynamic blocks, using the WP_Block class in render.php will give you access to the child data:

<?php
/**
 * Render Custom Block
 *
 * @param  array    $atts    Block Attributes
 * @param  string   $content Block Content (InnerBlocks)
 * @param WP_Block $block_class Block Class Instance
 * @return string          Block Markup
 */
function render( $atts, $content, $block_class ) {
	// Check how many inner blocks are available
	$item_count = $block_class->inner_blocks->count();
	if ( empty( $item_count ) ) {
		return '';
	}
	ob_start();
	?>
	<div>
		<?php
		foreach( $block_class->inner_blocks as $_block ) {
			// Get the inner block data
			$inner_block = $block_class->inner_blocks->current(); 
			
			// Holds the inner block attribute
			$attribute   = $inner_block->attributes;

			// This will display the attributes data
			var_dump( $attribute );

			// increase the index in the WP_Block_List class used to retrieve the current block
			$block_class->inner_blocks->next();
		}

		// reset the index in the WP_Block_List class to the initial state
		$block_class->inner_blocks->rewind();
		?>
	</div>
	<?php
	return ob_get_clean();
}

Adding Context

Using the Block Context feature, you can specify what data can transverse between Inner Blocks.

From within registerBlockType, add a providesContext block to the block that wants to share data:

registerBlockType( 'my-plugin/record', {
    // ... cut ...

    attributes: {
        recordId: {
            type: 'number',
        },
    },

    providesContext: {
        'my-plugin/recordId': 'recordId',
    },
...

In the receiving block, add a useContext statement:

registerBlockType( 'my-plugin/record-title', {
    title: 'Record Title',
    category: 'widgets',

    usesContext: [ 'my-plugin/recordId' ],
    edit( { context } ) {
        return 'The record ID: ' + context[ 'my-plugin/recordId' ];
    },
...

For all the php folks out there:

register_block_type( 'my-plugin/record-title', array(
    'render_callback' => function( $attributes, $content, $block ) {
        return 'The current record ID is: ' . $block->context['my-plugin/recordId'];
    },
) );

For more information and a complete example, see the Developer Reference Guide on Context.