InnerBlocks with ACF blocks

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.

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.

$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:

  1. 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.
  2. 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>';

Join Our Team at CultivateWP

We design and build high-performance custom WordPress themes for market leading web publishers. We're looking for designers & developers to join our team.

Learn More

Bill Erickson

Bill Erickson is a freelance WordPress developer and a contributing developer to the Genesis framework. For the past 14 years he has worked with attorneys, publishers, corporations, and non-profits, building custom websites tailored to their needs and goals.

Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk

Reader Interactions

Comments

    • 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.

  1. 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.

  2. 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.

  3. 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.

  4. 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

  5. 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.

  6. 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.

  7. 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).

  8. 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.

        • 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.

Leave A Reply