Create a Custom Gutenberg Block with Advanced Custom Fields

In a recent tweet I shared my experience in experimenting with Advanced Customer Fields and the new Gutenberg editor being released with WordPress 5.0. People seemed interested in it enough that I’ve decided to document my process in this blog post. Hopefully it will help others looking to do the same.

Let’s look at the process of creating a custom hero/banner block.

Installing ACF Pro

First things first, we’ll need a copy of ACF Pro. At the time that I’m writing this, Gutenberg is still in beta and only the most recent ACF Pro beta supports Gutenberg blocks.

You can download the latest ACF Pro beta by going to your ACF account and under “Licenses & Downloads” select “See all versions”.

Installing Gutenberg

You can download the Gutenberg plugin from the WordPress plugin directory. Gutenberg will be included in WordPress 5.0.

Registering your first block

Just as you would register a custom post type, the acf_register_block() function allows you to register a custom block with Gutenberg. There’s a great blog post on the ACF site that instructs you on how to use the new acf_register_block() function. It’s self explanatory, but I recommend looking it over.

functions.php

// Register Custom Blocks
add_action('acf/init', 'my_acf_init');
function my_acf_init() {
	
	// check function exists
	if( function_exists('acf_register_block') ) {
		
		// register a hero block
		acf_register_block(array(
			'name'				=> 'hero',
			'title'				=> __('Hero'),
			'description'		=> __('A custom hero block.'),
			'render_callback'	=> 'my_acf_block_render_callback',
			'category'			=> 'formatting',
			'icon'				=> 'admin-comments',
			'keywords'			=> array( 'hero', 'banner ),
		));
	}
}

Next we’ll tell ACF where to find our block templates. You can place this just after acf_register_block() function in functions.php.

functions.php

function my_acf_block_render_callback( $block ) {
	
	// convert name ("acf/testimonial") into path friendly slug ("testimonial")
	$slug = str_replace('acf/', '', $block['name']);
	
	// include a template part from within the "template-parts/block" folder
	if( file_exists(STYLESHEETPATH . "/template-parts/blocks/content-{$slug}.php") ) {
		include( STYLESHEETPATH . "/template-parts/blocks/content-{$slug}.php" );
	}
}

Creating the hero ACF fields

Now the fun part. Let’s create our hero fields within ACF.

I like prefixing my block field groups with “Block:” so that they’re grouped together amongst other fields groups. Our hero is going to support a heading, body text, and either a background image or solid color. Additionally, things like text color and alignment would be useful too.

Within the Background Image and Background Color fields, I’ve enabled conditional logic to allow the user to choose between adding either an image or a solid color to their hero. Make sure to set the appropriate values for both the image and color fields.

If you’d like, you can use this ACF fields export for the exact fields I used in this example:

functions.php

if( function_exists('acf_add_local_field_group') ):
acf_add_local_field_group(array(
'key' => 'group_5be4feb5bc536',
'title' => 'Block: Code Sample',
'fields' => array(
array(
'key' => 'field_5be598caff702',
'label' => 'File Name',
'name' => 'file_name',
'type' => 'text',
'instructions' => '',
'required' => 0,
'conditional_logic' => 0,
'wrapper' => array(
'width' => '',
'class' => '',
'id' => '',
),
'default_value' => '',
'placeholder' => '',
'prepend' => '',
'append' => '',
'maxlength' => '',
),
array(
'key' => 'field_5be4ff5230179',
'label' => 'Language',
'name' => 'language',
'type' => 'button_group',
'instructions' => '',
'required' => 0,
'conditional_logic' => 0,
'wrapper' => array(
'width' => '',
'class' => '',
'id' => '',
),
'choices' => array(
'html' => 'HTML',
'css' => 'CSS',
'php' => 'PHP',
'javascript' => 'JS',
),
'allow_null' => 0,
'default_value' => '',
'layout' => 'horizontal',
'return_format' => 'value',
),
array(
'key' => 'field_5be4ffd13017a',
'label' => 'Code',
'name' => 'code',
'type' => 'textarea',
'instructions' => '',
'required' => 0,
'conditional_logic' => 0,
'wrapper' => array(
'width' => '',
'class' => '',
'id' => '',
),
'default_value' => '',
'new_lines' => '',
'maxlength' => '',
'placeholder' => '',
'rows' => '',
),
),
'location' => array(
array(
array(
'param' => 'block',
'operator' => '==',
'value' => 'acf/code',
),
),
),
'menu_order' => 0,
'position' => 'normal',
'style' => 'default',
'label_placement' => 'top',
'instruction_placement' => 'label',
'hide_on_screen' => '',
'active' => 1,
'description' => '',
));
endif;

Hero template markup

Next, let’s build out the hero template markup. 

template-parts/blocks/content-hero.php

<?php
/**
* Block Name: Hero
*
* This is the template that displays the hero block.
*/
$backgroundType = get_field('background_type');
if(get_field('background_image')) {
$backgroundImage = get_field('background_image');
$backgroundImageURL = $backgroundImage['url'];
} else {
// Sets a default image as a placeholder
$backgroundImageURL = "https://source.unsplash.com/1600x900/?macbook";
}
if(get_field('background_color')) {
$backgroundColor = get_field('background_color');
}
// create id attribute for specific styling
$id = 'hero-' . $block['id'];
// create align class ("alignwide") from block setting ("wide")
$align_class = $block['align'] ? 'align' . $block['align'] : '';
?>
<div 
class="<?php echo $align_class; ?> px-2 pt-4 mb-4" 
id="<?php echo $id; ?>">
<div 
class="relative mx-auto rounded-t shadow bg-cover bg-center mb-4 flex justify-center items-center"  
<?php if($backgroundType == 'image'): ?>
style="background-image: url('<?php echo $backgroundImage['url']; ?>'); height: 20rem;"
<?php else: ?>
style="background-color: <?php echo $backgroundColor; ?>; height: 20rem;"
<?php endif; ?>
>
<div class="text-center px-3">
<h1 class="font-sans font-bold text-3xl text-white"><?php the_field('heading'); ?></h1>
<?php if(get_field('body')): ?>
<p class="font-sans text-xl text-white mb-0"><?php the_field('body'); ?></p>
<?php endif; ?>
</div>
</div>
</div>

Create a placeholder hero

I’m highlighting a bit of code from above that I was excited about. Instead of having a blank hero at the start,  I wanted the user to see a fully populated placeholder hero that they could edit.

Since I can’t set a default image in ACF, I’ll check to see if the background image has been set yet. If not, I’ll set the url used in the template to a placeholder image from my media library. 

template-parts/blocks/content-hero.php

<?php
if(get_field('background_image')) {
$backgroundImage = get_field('background_image');
$backgroundURL = $backgroundImage['url'];
} else {
$backgroundURL ="/wp-content/uploads/2018/11/default-hero-image.jpg";
}

Background image or color?

A quick if/else statement lets us print the appropriate inline styles to dynamically configure each hero block.

template-parts/blocks/content-hero.php

<?php if($backgroundType == 'image'): ?>
style="background: url('<?php echo $backgroundURL; ?>') center center no-repeat; background-size: cover; height: 20rem;"
<?php else: ?>
style="background-color: <?php echo $backgroundColor; ?>; height: 20rem;"
<?php endif; ?>

Styling your hero block

If you’re using Scss, and you should be, you can create a /blocks directory in your Scss folder. That way you can import all your blocks into your primary theme Scss file as well as a separate admin.scss which you can enqueue just for the editor.

functions.php

<?php
// Enqueue your block styles in wp-admin
function your_theme_admin_styles() {
wp_enqueue_style('admin-blocks', get_theme_file_uri('/css/admin.css'), array(), '20180820');
}
add_action( 'admin_enqueue_scripts', 'your_theme_admin_styles' );

In my theme, I’m leveraging Tailwind CSS to rapidly style UI components. With a handful of utility classes, I can fully and responsively style my hero block without adding a single line of custom CSS. That means I’m not enqueueing any block specific styles. I’m only enqueueing Tailwind CSS; both on the frontend and admin.

Using the hero block

From the Gutenberg editor, I’ll add in my new hero.

From the start, here’s what we get. Notice this is not a screenshot, but the actual hero block in action.

Hero Heading

Hero body text

Here’s what our initial editing experience looks like. I haven’t set a background image yet, so I get a lovely placeholder courtesy of unsplash.com. The heading and body text have default values.

For our next hero, let’s choose a solid background color instead.

Here’s what we get.

Hero Heading

Hero body text

Setting the size of our hero

Gutenberg comes with some built in sizing capabilities. We have “wide width” and “full width”. Here’s what a wide width hero looks like.

Hero Heading

Hero body text

And here’s a full width hero.

Hero Heading

Hero body text

Closing thoughts

All in all, I’m really happy with my initial experience with Gutenberg. I think it will be a welcomed change for most users spending time in the editor. I’m especially excited to see what some of my clients do with the blocks that I provide them in their web projects.

This theme was built with:

Starter Theme: WP rig by Morten Rand-Hendriksen and other contributors. WP rig is a progressive WordPress started theme with a modern build process.

CSS Framework: Tailwind CSS is a utility-first CSS framework
for rapid UI development.

JavaScript: This year I replaced jQuery in all of my WordPress themes with Vue.js. Encapsulating your theme in a simple Vue instance makes handling things like modals, dropdown menus, and form submissions a breeze. It’s more maintainable and enjoyable to write.

Custom Fields: Advanced Custom Fields enables WordPress developers to create highly customizable backend fields to produce content on the frontend using HTML/PHP templates.

Editor: Gutenberg is the new editor being released in WordPress 5.0.