My favorite new feature in Advanced Custom Fields 5.9 is support for InnerBlocks. This allows you to insert any block (core or custom) inside your ACF block.
Rather than having to create your own fields for Title, Content, and Button in your custom block, you can simply insert <InnerBlocks />
and use the block editor to build the content inside the block.
Table of Contents
How to use InnerBlocks
When registering your ACF block, include 'jsx' => true
in the supports
array.
acf_register_block_type( array(
'title' => __( 'About', 'client_textdomain' ),
'name' => 'about',
'render_template' => 'partials/blocks/about.php',
'mode' => 'preview',
'supports' => [
'align' => false,
'anchor' => true,
'customClassName' => true,
'jsx' => true,
]
));
In your template partial for the block, include <InnerBlocks />
where you would like the editable block area to appear.
$classes = ['block-about'];
if( !empty( $block['className'] ) )
$classes = array_merge( $classes, explode( ' ', $block['className'] ) );
$anchor = '';
if( !empty( $block['anchor'] ) )
$anchor = ' id="' . sanitize_title( $block['anchor'] ) . '"';
echo '<div class="' . join( ' ', $classes ) . '"' . $anchor . '>';
echo '<div class="block-about__inner">';
echo '<div class="block-about__content">';
echo '<InnerBlocks />';
echo '</div>';
echo '<div class="block-about__image">';
echo wp_get_attachment_image( get_field( 'image' ), 'be_thumbnail_l' );
echo '</div>';
echo '</div>';
echo '</div>';
Default value for InnerBlocks
It’s helpful to fill the InnerBlocks field with default content so the block looks correct when first inserted.
We’re building a new site for Nice Kicks, and they often need to highlight the release date and details for new sneakers. We built a Release Info block that uses InnerBlocks for the content area.
Rather than just having an empty white box when they first insert the block, we pre-populate it with default content using a block template.
Inside the block’s template file, create a $template
array detailing which blocks should be added. Update <InnerBlocks />
to include the template.
You can find the available block attributes in a blocks.json file for each block in wp-includes/blocks.
$template = array(
array('core/heading', array(
'level' => 2,
'content' => 'Title Goes Here',
)),
array( 'core/paragraph', array(
'content' => '<strong>Colorway:</strong> <br /><strong>Style Code:</strong> <br /><strong>Release Date:</strong> <br /><strong>MSRP:</strong> ',
) )
);
echo '<div class="' . join( ' ', $classes ) . '"' . $anchor . '>';
echo '<InnerBlocks template="' . esc_attr( wp_json_encode( $template ) ) . '" />';
$form_id = get_option( 'options_be_release_info_form' );
if( !empty( $form_id ) && function_exists( 'wpforms_display' ) )
wpforms_display( $form_id, true, true );
echo '</div>';
Placeholders instead of default content
In the above example we set the starting content for the block. If you were to publish the post without changing the text, the default content would appear in the blocks.
Alternatively, you can use the placeholder
parameter to specify placeholder text. This will not be published, and when you select the field the placeholder text disappears.
I had two issues with placeholders, which is why I used default content instead:
- When you insert the block, the first block inside InnerBlocks is selected so its placeholder text is not visible. You have to insert the block then click outside the block to see the placeholder text.
- The placeholder field does not support HTML. In my use case, we used
<strong>
and<br />
to format the paragraph text, but that doesn’t work with the placeholder.
To use placeholders with the above example, change the $template
to
$template = array(
array('core/heading', array(
'level' => 2,
'placeholder' => 'Title Goes Here',
)),
array( 'core/paragraph', array(
'placeholder' => '<strong>Colorway:</strong> <br /><strong>Style #:</strong> <br /><strong>Release Date:</strong> <br /><strong>Price:</strong> ',
) )
);
And this was the result:
Limit the blocks available in InnerBlocks
You can limit which blocks can be inserted into your InnerBlocks field using the allowedBlocks
attribute.
Using the example above, I can limit the Release Info block to only include the heading and paragraph blocks:
$allowed_blocks = array( 'core/heading', 'core/paragraph' );
$template = array(
array('core/heading', array(
'level' => 2,
'content' => 'Title Goes Here',
)),
array( 'core/paragraph', array(
'content' => '<strong>Colorway:</strong> <br /><strong>Style Code:</strong> <br /><strong>Release Date:</strong> <br /><strong>MSRP:</strong> ',
) )
);
echo '<div class="' . join( ' ', $classes ) . '"' . $anchor . '>';
echo '<InnerBlocks allowedBlocks="' . esc_attr( wp_json_encode( $allowed_blocks ) ) . '" template="' . esc_attr( wp_json_encode( $template ) ) . '" />';
$form_id = get_option( 'options_be_release_info_form' );
if( !empty( $form_id ) && function_exists( 'wpforms_display' ) )
wpforms_display( $form_id, true, true );
echo '</div>';
Template lock with InnerBlocks
You can also limit the flexibility by locking the template.
Adding templateLock="all"
prevents inserting new blocks or removing/re-arranging current blocks
Adding templateLock="insert"
prevents inserting new blocks or removing current blocks, but you can re-arrange the current blocks.
I recently built an Icon Heading block. The icon can be selected in the block settings sidebar using a dynamic dropdown field.
I used InnerBlocks for the heading itself so it would have all the standard options for customizing the heading (change block style, change heading type to h3). I used templateLock="all"
so only the heading from my block template could be used in this block.
$classes = ['block-icon-heading'];
if( !empty( $block['className'] ) )
$classes = array_merge( $classes, explode( ' ', $block['className'] ) );
if( !empty( $block['align'] ) )
$classes[] = 'align' . $block['align'];
$anchor = '';
if( !empty( $block['anchor'] ) )
$anchor = ' id="' . sanitize_title( $block['anchor'] ) . '"';
$template = array(
array('core/heading', array(
'level' => 2,
'content' => 'Heading',
)),
);
echo '<div class="' . join( ' ', $classes ) . '"' . $anchor . '>';
$icon = get_field( 'dynamic_icon_category' );
if( !empty( $icon ) )
echo '<div class="icon-heading-wrap">' . be_icon( [ 'icon' => $icon, 'group' => 'category', 'size' => 38 ] ) . '</div>';
echo '<InnerBlocks template="' . esc_attr( wp_json_encode( $template ) ) . '" templateLock="all" />';
echo '</div>';
Michal Trykoszko says
Why bother using PHP if InnerBlocks are just a few lines of code in a native (React) way of creating Gutenberg blocks? Example of React-built InnerBlocks: https://pastebin.com/1uqZyXdt
Bill Erickson says
Many WordPress developers (myself included) are more comfortable with PHP than React. Also, the InnerBlocks component may be part of a larger/more complex block, so the actual React code required would be much more complicated.
Jason D Martino says
Can you put another ACF block within a parent ACF block that allows InnerBlocks? Can you put an ACF block within the 2 column Gutenberg block?
Bill Erickson says
Yes, you can insert any block (from core, ACF, or a plugin) inside the InnerBlocks area.
Yes, you can place an ACF block inside the core columns block.
Joe S says
So I tried using this example to limit which ACF blocks are available inside my custom block, and it doesn’t seem to do anything. I can place any block I want inside my custom block from the page editor, and that block renders on the front end.
The only real difference between your code and mine is that I am not using a template for my block. Would that be what’s causing the problem?
Bill Erickson says
I’m not sure, you might try using a template part and see if that fixes it. I haven’t run into any issues with restricting blocks in InnerBlocks but I only use template parts.
weart says
I tried it, and worked, until I need to create a template for cover block. I can’t set a template inside the cover block.
Bill Erickson says
It looks like this is a known issue. Personally, I avoid the complex core blocks like cover, media & text, and columns because they often change with each Gutenberg / WP update, breaking whatever customizations I have made.
Joel says
Hi Bill
Thanks for the tutorial. Do you know of a way to pass data through to an InnerBlock? E.g. the name of the parent block, or even just the fact that is has a parent?
I would like the HTML of a block to differ depending on whether it’s an InnerBlock or not.
Cheers
Joel
Bill Erickson says
No, I don’t believe there is a way to detect the block hierarchy from within the block.
Cathy Tibbles says
That would be really useful – have you tried using css classes?
Stijn De Mulder says
Hi Bill,
I’m trying to have different layouts for my innerblocks depending on value of ACF field (or block style).
Seems to work fine but only after refresh, the changes aren’t visible ‘live’. Any idea how to change this?
Or do you recommend two (or more) custom ACF blocks in stead of one with multiple options?
Have a nice day.
Bill Erickson says
How are you accessing the fields in your block? If you’re using `get_field()` you should see a live refresh as the fields are edited in the block. You might try asking ACF support to help troubleshoot the issue.
Stijn De Mulder says
Fields are fine, through get_field indeed.
Choice of InnerBlocks template depends on the Block Style, more of a visual approach.
I can see the classes change but the Innerblocks template only changes after save+refresh.
I read something about serverrender but not confident enough with the Node.js side of it.
Cathy Tibbles says
Thank you!! I use this almost every day and didn’t realize they have that feature – doh! This will make life so much easier!
Also – thank you for sharing your base child theme by the way – I went through it with a fine tooth comb. You’re really generous to share so much of your work! Have you tried to contribute the ‘block areas’ files/classes to core?
Bill Erickson says
Thank you, it’s great to hear my code and tutorials are helpful!
WordPress core is already working on moving widget areas to the block editor, so in 5.7 or 5.8 this functionality should be in core.
Spab Rice says
This was really helpfull, thank you very much. Just wondering if it is possible to insert multiple InnerBlocks.
For example I have a block with 2 columns and each column should contain an Innerblock with custom content.
Bill Erickson says
Unfortunately no, WordPress only allows a single instance of InnerBlocks inside of a block. I often use InnerBlocks for one column and ACF fields for the other. For instance, a “Content & Image” block will have InnerBlocks for content, an image upload field for the image, and a “Layout” dropdown to select the arrangement (Content|Image or Image|Content).
Grégoire Noyelle says
Hi Bill,
Thanks a lot for your post.
You can use Innerblock with the new Block Pattern functionality. It works great and you can have the default fields in place. And the preview in the Composition Gutenberg tab.
Here the handbook about Block Pattern:
https://developer.wordpress.org/block-editor/developers/block-api/block-patterns/
ToM says
Hi Bill,
thx for this post. We use acf with gutenberg blocks in our theme too. I just wondered how you managed the inline editing in the acf block. I always thought editing the fields of an acf gutenberg block is only possible in the right sidebar.
Bill Erickson says
If you use the “auto” mode it will display the field editor in the main content column when that block is selected.
ToM says
yes, but in your video it is inline editing the headline and so on. not the switch to the “form” view.
Bill Erickson says
What you’re seeing there is the InnerBlocks feature in WordPress, which is the focus of this article. If you include <InnerBlocks /> in your block, you can use the full block editor inside your custom block.