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>';
Petersen says
Is it possible to somehow remove heading 2, 4, 5 and 6 from the ‘core/heading’ ?
Bill Erickson says
I’m not aware of a way to remove specific heading types from the heading block.
Monir Hasan says
Hi Bill,
Thanks for your Help in Genesis. I am regularly learning from You.
I want to implement manual TOC like you. Will you please share? How can we implement TOC in Genesis/WordPress like you?
Bill Erickson says
Here’s the code for my table of contents block: https://www.billerickson.net/code/table-of-contents-block/
Monir Hasan says
Thank You 🙂
Dan Stramer says
Thanks Bill!
Do you know if there is a way to add gutenberg blocks to existing ACF fields.
I have a site with flexible fields which have other acf fields in them – text, images wysiwyg etc…
I would like to add a gallery block, into a wysiwyg area for instance, and since ACF’s editor is tinymce and not the block editor, would you know how to achieve this?
Thanks!
Dan
Bill Erickson says
No, the only way you can accomplish that is by using InnerBlocks in a custom ACF block. You could pre-populate the InnerBlocks area with the gallery block.
Florent says
Hi,
Thank you for the code.
For me, when InnerBlocks are before acf content, the acf output never appear in the preview.
If the innerBlocks tag is after, all acf output before it will appear in preview.
In the front end, all content is present.
in your example, if I wrote :
—————–
echo ”;
echo ”;
$form_id = get_option( ‘options_be_release_info_form’ );
if( !empty( $form_id ) && function_exists( ‘wpforms_display’ ) )
wpforms_display( $form_id, true, true );
echo ”;
——————
wpforms_display never display, but if I change order :
——————-
echo ”;
$form_id = get_option( ‘options_be_release_info_form’ );
if( !empty( $form_id ) && function_exists( ‘wpforms_display’ ) )
wpforms_display( $form_id, true, true );
echo ”;
echo ”;
———————-
it will
Any idea ?
wordpress: 5.7 and ACF 5.9.5
Thank you
Florent
Bill Erickson says
I think some of the HTML is getting stripped out. Can you post it as a GitHub Gist instead?
I haven’t had any issues similar to what you’re describing.
Chris says
I have created a gist to demonstrate this:
https://gist.github.com/cstielper/a72ca483319789a25800bd29effed768
Nothing after: echo ‘<InnerBlocks …' displays in the editor.
If you comment that line out, then the admin notice (and everything after it) appears in the editor.
Everything displays on the front-end as expected
Jason LeMahieu says
Hi Bill,
I’m curious if you have any ideas on how to use a Blacklist (rather than a whitelist) for what kinds of blocks are allowed in the InnerHTML? (There are really just a handful we’d like to stop, and dozens upon dozens that should be available.)
Cheers!
Chris says
I’ve been unable to find a way to set text alignment in a template. Using your example:
$template = array(
array(‘core/heading’, array(
‘level’ => 2,
‘content’ => ‘Heading’,
)),
);
I would expect to be able to pass in something like one of the following:
‘align’ => ‘center’,
‘text_align’ => ‘center’,
‘align_text’ => ‘center’,
but haven’t had much luck.
Any ideas?
Bill Erickson says
Try textAlign.
I’ve found the simplest way to figure this stuff out is by looking at /wp-includes/blocks/ , select your block, then view the block.json file.
Here’s the one for headings: https://github.com/WordPress/WordPress/blob/master/wp-includes/blocks/heading/block.json
Chris says
Perfect! Thanks so much for the help.
Good tip on the block.json file (bookmarked for future use).
Love your blog, read it often.
Birdbrain Solutions says
What would be the point of using ACF with gutenberg considering that you can just create a block pattern instead? The only thing ACF seems to be doing in this is providing a fixed layout and a way to update the image. I would really like know your thoughts
Thanks for the article! And have a great day!
Bill Erickson says
ACF lets you build a custom block, while block patterns only let you predefine arrangements of existing blocks.
Three main benefits to custom blocks are:
1. Separation of design and content. Let the client enter the appropriate content (ex: name, profile photo, job title, description) and the block will handle styling it appropriately. You can use semantically correct markup rather than “pick the h4 so the font size is smaller”.
2. Better control over responsive styling when compared to columns block.
3. Ability to define a restricted set of blocks for use in InnerBlocks and/or a fixed template for how those blocks should appear. For instance, I might have a “Testimonial Carousel” block that only allows core/quote to be inserted into InnerBlocks so I can style it appropriately as a carousel. I don’t have to worry about what happens if the client sticks an image or a cover block inside the Testimonial Carousel block.
Rob says
Hello Bill,
I am trying to use the core columns in the innerblock. I have also restricted use to only the heading, paragraph, and columns. That problem I ran into is that once you place columns (inside the acf block), it will allow all blocks again.
Is there some way to nest these restrictions on the columns inside the innerblock, without affecting it globally?
Bill Erickson says
I don’t believe there’s a way to recursively restrict what blocks can appear. In other words, if one of the blocks you allow contains InnerBlocks, then that InnerBlocks will allow anything.
Ben Upham says
Hi,
This is very helpful post. I’ve noticed that when using ACF blocks in an InnerBlocks template, any default values in that inner ACF block do not immediately appear in the editor when the block is added. For example, if I have an acf/header block with data => [‘title’ => ‘Some string’] the block is added with the correct string, but the title won’t actually appear in preview until I update the value by adding a space or something. Have you noticed this?
Thank you,
Ben
Bill Erickson says
I haven’t noticed this, but I don’t think I’ve tried this specific setup. You might try reaching out to ACF support.
Nelson Amaya says
Hi Bill, this is a brilliant article, thank you so much!
I wonder if there’s a way to manage the inner blocks properties, e.g. if I add a Paragraph block within my ACF block, is there a way to disable the “alignment” property just on this inner block?
Thanks!
Bill Erickson says
I don’t think you can disable properties in InnerBlocks that are not already disabled globally. But you can specify default content and set the default properties the way you want. For instance, you could have your InnerBlocks pre-populated with an h2 heading and paragraph that are both center aligned.