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.

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:

  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>';

Bill Erickson

Bill Erickson is the co-founder and lead developer at CultivateWP, a WordPress agency focusing on high performance sites for web publishers.

About Me
Ready to upgrade your website?

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

Let's Talk

Reader Interactions

Comments are closed. Continue the conversation with me on Twitter: @billerickson

Comments

    • Bill Erickson says

      I’m not aware of a way to remove specific heading types from the heading block.

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

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

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

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

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

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

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

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

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