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

  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?

Leave A Reply