How to build a Gutenberg block with inner blocks and InnerBlocks templates?

Building a Gutenberg block that harnesses the power of inner blocks and templates might sound a bit complex at first, but it’s a game-changer for creating flexible and user-friendly content structures. Essentially, you’re creating a container block where users can add and arrange other blocks, and with templates, you can pre-define what those inner blocks are and how they’re laid out. This significantly streamlines the content creation process and ensures consistency across your site. Let’s dig into how you can achieve this.

Before we dive into inner blocks, let’s make sure we have a foundational block setup. We’ll be using create-guten-block or building manually with @wordpress/scripts. For this guide, we’ll assume you have a basic block structure ready.

Registering Your Block

Every block needs a registration. In your block.json file, beyond the usual attributes, there’s nothing special to add for inner blocks directly. The magic happens in your edit.js and save.js files.

“`json

// block.json

{

“$schema”: “https://schemas.wp.org/trunk/block.json”,

“apiVersion”: 2,

“name”: “your-plugin/inner-block-container”,

“version”: “1.0.0”,

“title”: “Inner Block Container”,

“category”: “design”,

“icon”: “layout”,

“description”: “A container block that holds other blocks.”,

“supports”: {

“html”: false,

“align”: true

},

“textdomain”: “your-plugin”,

“editorScript”: “file:./index.js”,

“editorStyle”: “file:./index.css”,

“style”: “file:./style-index.css”

}

“`

This block.json sets up a standard block. Now we move on to the actual implementation.

If you’re looking to deepen your understanding of creating Gutenberg blocks with inner blocks and InnerBlocks templates, you might find the article on building custom Gutenberg blocks particularly helpful. This resource provides a comprehensive guide that complements the techniques discussed in the original article. For more insights, you can check out the related article here.

Introducing InnerBlocks for Flexible Content

The InnerBlocks component is the core of what allows your block to act as a container. It’s provided by @wordpress/block-editor.

Importing InnerBlocks

First, you’ll need to import InnerBlocks into your edit.js file:

“`javascript

// src/edit.js

import { useBlockProps, InnerBlocks } from ‘@wordpress/block-editor’;

import ‘./editor.scss’;

export default function Edit() {

const blockProps = useBlockProps();

return (

// InnerBlocks will go here

);

}

“`

Implementing InnerBlocks in edit.js

Now, let’s place InnerBlocks inside your JSX. By default, InnerBlocks will allow any block to be added.

“`javascript

// src/edit.js (simplified)

import { useBlockProps, InnerBlocks } from ‘@wordpress/block-editor’;

import ‘./editor.scss’;

export default function Edit() {

const blockProps = useBlockProps();

return (

Inner Block Container

);

}

“`

With just this much, you’ll see an “Add block” button inside your container block in the editor. Users can then add any available block.

Saving Inner Blocks: The save.js Side

The save.js file is where you render the saved output for the frontend. For inner blocks, you’ll use InnerBlocks.Content.

“`javascript

// src/save.js

import { useBlockProps, InnerBlocks } from ‘@wordpress/block-editor’;

export default function Save() {

const blockProps = useBlockProps.save();

return (

Inner Block Container

);

}

“`

InnerBlocks.Content is crucial. It retrieves the serialized HTML of all the inner blocks added by the user and renders them. You don’t need to manually iterate or save individual block attributes here; InnerBlocks.Content handles that for you.

If you’re looking to enhance your WordPress site while building custom Gutenberg blocks with inner blocks and InnerBlocks templates, you might also find it beneficial to explore how site performance impacts user experience. A great resource on this topic is an article that discusses optimizing your site using Google PageSpeed Insights, which can help ensure that your custom blocks load efficiently. You can read more about it in this insightful piece on Google PageSpeed Insights. This knowledge can complement your block-building skills by ensuring that your site remains fast and responsive.

Controlling Inner Blocks: allowedBlocks and orientation

While allowing any block is flexible, sometimes you want to restrict what can be added. This is where allowedBlocks comes in. You can also control the orientation of the inserter.

Specifying allowedBlocks

The allowedBlocks prop takes an array of block names (e.g., 'core/paragraph', 'core/heading') that are permitted within your container.

“`javascript

// src/edit.js (with allowedBlocks)

import { useBlockProps, InnerBlocks } from ‘@wordpress/block-editor’;

import ‘./editor.scss’;

export default function Edit() {

const blockProps = useBlockProps();

const MY_ALLOWED_BLOCKS = [ ‘core/paragraph’, ‘core/heading’, ‘core/image’ ];

return (

Inner Block Container

allowedBlocks={ MY_ALLOWED_BLOCKS }

/>

);

}

“`

Now, when a user clicks “Add block” inside your container, only Paragraph, Heading, and Image blocks will be available in the inserter.

Setting orientation for the Inserter

The orientation prop controls how the block inserter displays, either 'horizontal' or 'vertical'. This is a visual preference.

“`javascript

// src/edit.js (with orientation)

import { useBlockProps, InnerBlocks } from ‘@wordpress/block-editor’;

import ‘./editor.scss’;

export default function Edit() {

const blockProps = useBlockProps();

const MY_ALLOWED_BLOCKS = [ ‘core/paragraph’, ‘core/heading’, ‘core/image’ ];

return (

Inner Block Container

allowedBlocks={ MY_ALLOWED_BLOCKS }

orientation=”horizontal” // or “vertical”

/>

);

}

“`

This will change how the block inserter appears when you’re adding blocks within your container.

Pre-defining Content with templates

Templates are where the true power of structure and consistency comes in. You can define a default set of blocks and even their attributes that will appear when your container block is first added.

Understanding the Template Structure

A template is an array of arrays. Each inner array represents a block, with the first item being the block name and the second being an object of its attributes.

“`javascript

// Example Template

const MY_TEMPLATE = [

[ ‘core/heading’, { placeholder: ‘Add your title here’, level: 2 } ],

[ ‘core/paragraph’, { placeholder: ‘Write your content here.’ } ],

[ ‘core/image’, {} ],

];

“`

Implementing templates in edit.js

You pass this template array to the template prop of InnerBlocks.

“`javascript

// src/edit.js (with template)

import { useBlockProps, InnerBlocks } from ‘@wordpress/block-editor’;

import ‘./editor.scss’;

export default function Edit() {

const blockProps = useBlockProps();

const MY_TEMPLATE = [

[ ‘core/heading’, { placeholder: ‘Add a hero headline’, level: 2 } ],

[ ‘core/paragraph’, { placeholder: ‘Describe your hero content.’ } ],

[ ‘core/buttons’, {}, [ // InnerBlocks in a child block!

[ ‘core/button’, { text: ‘Learn More’ } ]

]]

];

return (

Hero Section Container

template={ MY_TEMPLATE }

/>

);

}

“`

Now, when you add your ‘Inner Block Container’ to the editor, it will automatically populate with a heading, paragraph, and a button block, pre-filled with place holders and specific attributes. This is incredibly useful for creating consistent sections across a site.

templateLock: Preventing Changes to the Template

Sometimes you want users to have a predefined structure, but not be able to modify it too much. templateLock is your friend here.

templateLock="all"

templateLock="all" prevents users from adding new blocks, reordering existing blocks, or deleting blocks within the container. They can only modify the content of the pre-existing blocks.

“`javascript

// src/edit.js (with templateLock=”all”)

import { useBlockProps, InnerBlocks } from ‘@wordpress/block-editor’;

import ‘./editor.scss’;

export default function Edit() {

const blockProps = useBlockProps();

const MY_TEMPLATE = [

[ ‘core/heading’, { placeholder: ‘Add a hero headline’, level: 2 } ],

[ ‘core/paragraph’, { placeholder: ‘Describe your hero content.’ } ],

];

return (

Locked Hero Section

template={ MY_TEMPLATE }

templateLock=”all”

/>

);

}

“`

This is perfect for rigid layouts where the structure should never change.

templateLock="insert"

templateLock="insert" allows users to reorder and delete existing blocks but prevents them from adding new blocks that aren’t part of the initial template.

“`javascript

// src/edit.js (with templateLock=”insert”)

import { useBlockProps, InnerBlocks } from ‘@wordpress/block-editor’;

import ‘./editor.scss’;

export default function Edit() {

const blockProps = useBlockProps();

const MY_TEMPLATE = [

[ ‘core/heading’, { placeholder: ‘Section Title’ } ],

[ ‘core/paragraph’, { placeholder: ‘Section content.’ } ],

];

return (

Semi-Locked Section

template={ MY_TEMPLATE }

templateLock=”insert”

/>

);

}

“`

This offers a bit more flexibility while still maintaining control over the types of blocks that can exist in the container.

templateLock="false" or omitted

If templateLock is false (or omitted), users have full control: they can add, reorder, and delete blocks within the container, even if a template was initially applied. The template serves as a starting point.

Styling Your Inner Block Container

Styling an inner block container is similar to styling any other block, with a few considerations.

Editor Styles for Clarity

In the editor, you’ll likely want to visually delineate your container. This makes it clear to the user what’s happening.

“`scss

// src/editor.scss

.wp-block-your-plugin-inner-block-container {

border: 2px dashed #ccc;

padding: 20px;

background-color: #f0f0f0;

margin-bottom: 20px;

// You can target InnerBlocks directly if needed

.block-editor-inner-blocks__wrapper {

// Add specific styles here if you want to visually adjust the inner block area itself

}

}

“`

This gives your container a visual border and background in the editor, making it easier to work with.

Frontend Styles for Layout

The frontend styles will dictate how the container and its inner blocks are presented to the site visitor. Often, you’ll use flexbox or grid to arrange the inner blocks.

“`scss

// src/style.scss

.wp-block-your-plugin-inner-block-container {

background-color: #ffffff;

padding: 30px;

box-shadow: 0 4px 8px rgba(0,0,0,0.1);

display: flex; // Example for horizontal layout

gap: 20px; // Spacing between inner blocks

@media (max-width: 768px) {

flex-direction: column; // Stack vertically on small screens

}

}

“`

Remember, InnerBlocks.Content simply outputs the HTML of its child blocks. You’ll need to use CSS on your container to arrange those children.

Advanced Considerations and Best Practices

While the above covers the core functionality, there are a few more things to keep in mind.

Nested Inner Blocks

Yes, you can have inner blocks within inner blocks! If you define a template for your container block, and one of the blocks in that template is another block that itself uses InnerBlocks (like the core/buttons block), then you can define a template for that nested InnerBlocks instance as well.

Consider the core/buttons block. It uses InnerBlocks to contain individual core/button blocks.

“`javascript

const MY_TEMPLATE_WITH_NESTED_BUTTONS = [

[ ‘core/heading’, { placeholder: ‘Team Section’ } ],

[ ‘core/paragraph’, { placeholder: ‘Meet our amazing team.’ } ],

[ ‘core/buttons’, {}, [ // This is the core/buttons block

[ ‘core/button’, { text: ‘Contact Us’, url: ‘#’ } ], // This is the core/button within core/buttons

[ ‘core/button’, { text: ‘View Portfolio’, url: ‘#’, className: ‘is-style-outline’ } ]

] ],

];

“`

The third item in the MY_TEMPLATE_WITH_NESTED_BUTTONS array is an array representing the core/buttons block. The third element of that array is yet another array, which serves as the template for the InnerBlocks within the core/buttons block. It’s a bit mind-bending initially, but incredibly powerful for pre-structuring complex layouts.

Dynamic Inner Block Behavior with useSelect and dispatch

Sometimes you need your container block to react to changes in its inner blocks, or vice-versa. You can leverage the @wordpress/data package for this.

Checking for Empty Inner Blocks

You might want to display a message or apply a specific style if the container has no inner blocks.

“`javascript

import { useBlockProps, InnerBlocks } from ‘@wordpress/block-editor’;

import { useSelect } from ‘@wordpress/data’;

import ‘./editor.scss’;

export default function Edit( { clientId } ) {

const blockProps = useBlockProps();

const hasInnerBlocks = useSelect( ( select ) =>

select( ‘core/block-editor’ ).getBlocks( clientId ).length > 0,

[ clientId ]

);

return (

Your Inner Block Container

{ ! hasInnerBlocks &&

Start adding blocks here!

}

// … props like template, allowedBlocks, etc.

/>

);

}

“`

Here, useSelect is used to check the number of child blocks associated with the current block’s clientId. If none exist, a placeholder message is displayed.

Custom Placeholders and Appenders

For a really polished editor experience, you might want to replace the default InnerBlocks “Add block” button with something more specific to your block.

You can create a custom BlockAppender or pass a custom renderAppender function to InnerBlocks. This is typically used when you want to control the exact position or appearance of the inserter. However, for most cases, the default appender is sufficient and recommended for consistency with the rest of Gutenberg.

“`javascript

import { useBlockProps, InnerBlocks, BlockList, store as blockEditorStore } from ‘@wordpress/block-editor’;

import { useSelect, useDispatch } from ‘@wordpress/data’;

import { plus } from ‘@wordpress/icons’;

import { Button } from ‘@wordpress/components’;

import { __ } from ‘@wordpress/i18n’;

// … (inside Edit function)

const blockProps = useBlockProps();

const { insertBlock } = useDispatch( blockEditorStore );

// Custom appender example (can be passed to renderAppender prop)

const MyCustomAppender = () => (

className=”my-custom-add-block-button”

onClick={ () => {

// Logic to insert a specific block, or open the inserter

insertBlock( createBlock(‘core/paragraph’), undefined, clientId );

} }

icon={ plus }

label={ __( ‘Add Paragraph’, ‘your-plugin’ ) }

>

{ __( ‘Add Block’, ‘your-plugin’ ) }

);

return (

template={ MY_TEMPLATE }

templateLock=”all”

renderAppender={ hasInnerBlocks ? InnerBlocks.DefaultBlockAppender : MyCustomAppender }

/>

);

“`

This example shows how you could create a custom appender button. If hasInnerBlocks is false, our custom appender appears, allowing a user to click and immediately add a paragraph block. Otherwise, the default appender (which appears when hovering over an empty space between blocks) is used. This is a more advanced customization and often not strictly necessary.

Building Gutenberg blocks with inner blocks and templates opens up a world of possibilities for creating dynamic, consistent, and user-friendly content structures. By mastering InnerBlocks, templates, and templateLock, you can greatly enhance the editing experience and the quality of content on your WordPress sites. Start simple, then build up the complexity as you get more comfortable.