The ACF Blocks feature was first introduced in ACF PRO 5.8. Shortly thereafter, Elliot Condon made a video showing how you could use ACF Blocks to create a slider block in just 30 minutes.
“We recently released a new and exciting feature in ACF PRO to help PHP developers take full advantage of the new Block based editor…it’s called ACF Blocks and it makes light work of block type development. To demonstrate just how easy this PHP framework is to work with, I set myself a coding challenge to build a custom image slider in just 30 minutes.
It was a great resource, but it uses the original method for creating ACF Blocks. Since ACF 6.0, the recommended approach is to instead configure ACF Blocks with block.json. You can see the original video below, then read on for an updated tutorial and video showing how to use ACF Blocks to create an image slider block that uses block.json
.
Creating a Slider Block in ACF Blocks: Step-by-Step Tutorial
In this video, Damon Cook walks us through how to build a custom ACF slider block. The end result is an ACF Block that serves as an easily updatable image carousel. The video covers setting up a custom WordPress plugin to hold the block, installing and setting up Swiper 3rd-party dependency for slider interactivity, nesting multiple ACF Blocks with InnerBlocks, and setting up ACF fields and assigning them to the new slider block. A step-by-step tutorial follows the video below.
This tutorial assumes that you’re creating a custom plugin to hold your new plugin, but it’s also possible to use a child theme with minimal changes.
Setting Up the Plugin
The process used to create any ACF Block is very similar. If this is your first time working with ACF Blocks, we suggest starting with our tutorial on how to create your first ACF Block.
The very first step is to create a new plugin, complete with the standard WordPress plugin header. Next, register two ACF Blocks, a “slider” block and a “slide” block. We’ll discuss the differences between these two later on.
function wpe_slider_register_blocks() {
/**
* We register our block's with WordPress's handy
* register_block_type();
*
* @link https://developer.wordpress.org/reference/functions/register_block_type/
*/
register_block_type( __DIR__ . '/blocks/slider/' );
register_block_type( __DIR__ . '/blocks/slide/' );
}
add_action( 'init', 'wpe_slider_register_blocks', 5 );
This should be placed near the top of the file, after any necessary includes or requires, but before any other functional code.
Creating the Block Structure
Next, create a blocks
directory within your plugin, and a further two directories within that: blocks/slider
and blocks/slide
. In each directory, create two files: block.json
and template.php
. It’s okay to leave these blank for now. We’ll start filling them in the next section.
Defining Block Properties
In this step, we’ll define the properties for our slider and slide blocks using block.json
files. These files are crucial as they tell WordPress and ACF how to handle our custom blocks.
Slider Block
First, let’s create the block.json
file for our slider block. This file will be located in the blocks/slider
directory.
{
"name": "wpe/slider",
"apiVersion": 2,
"title": "Slider",
"description": "Display a slider.",
"category": "media",
"icon": "slides",
"keywords": ["slider", "slideshow", "carousel"],
"acf": {
"mode": "preview",
"renderTemplate": "template.php"
},
"attributes": {
"align": {
"type": "string",
"default": "full"
},
"style": {
"type": "object",
"default": {
"dimensions": {
"minHeight": "30vh"
}
}
}
},
"supports": {
"align": ["full", "wide"],
"anchor": true,
"className": true,
"color": {
"text": true,
"background": true
},
"dimensions": {
"minHeight": true
},
"mode": false
},
"example": {
"attributes": {
"align": {
"type": "string",
"default": "full"
}
},
"innerBlocks": [
{
"name": "wpe/slide",
"attributes": {
"className": "swiper-slide",
"content": "Slide #1"
}
},
{
"name": "wpe/slide",
"attributes": {
"className": "swiper-slide",
"content": "Slide #2"
}
}
]
},
"editorScript": ["swiper-module-editor"],
"editorStyle": "file:./editor.css",
"style": ["file:./../../swiper/swiper-bundle.css", "file:./style.css"],
"viewScript": ["swiper-module-front"],
"styles": [
{
"name": "default",
"label": "Default",
"isDefault": true
},
{
"name": "complex",
"label": "Complex"
}
]
}
Let’s break down the key properties:
name
: This is a unique identifier for the block. We’re using “wp/slider” to namespace it.title
anddescription
: These appear in the block inserter to help users understand the block’s purpose.category
: We’ve placed it in the “media” category as it’s primarily for displaying images.icon
: This visual identifier helps users quickly spot the block in the editor.keywords
: These help users find the block when searching in the inserter.acf
: This section is specific to ACF blocks. We’re setting it to “preview” mode and specifying the template file.supports
: This defines what WordPress block features our slider supports, like alignment and color settings.
Slide Block
Now, let’s create the block.json
file for our slide block in the blocks/slide
directory.
{
"name": "wpe/slide",
"apiVersion": 2,
"title": "Slide",
"description": "Add slides to a slider",
"category": "media",
"icon": "format-image",
"keywords": ["slider", "slideshow", "carousel"],
"acf": {
"mode": "preview",
"renderTemplate": "template.php"
},
"attributes": {
"slideImg": {
"type": "object",
"default": {
"src": "new-slide.jpg"
}
},
"slideTitle": {
"type": "string",
"default": "Slide Title"
}
},
"parent": [ "wpe/slider" ],
"supports": {
"align": false,
"anchor": true,
"className": true,
"color": {
"text": true,
"background": true
},
"reusable": false
}
}
The slide block is simpler because it’s designed to work only within the slider block:
parent
: This crucial property ensures that the slide block can only be used inside the slider block.supports
: We’ve disabled the “reusable” feature as slides are meant to be unique to each slider.
By defining these properties, we’re creating a structure where users can add a slider block to their content, and then add individual slide blocks within it. This nested approach allows for a more intuitive editing experience and keeps the slider functionality encapsulated.
Slide Block Custom Fields
Create a new field group with the two custom fields needed for the slide block: an Image field with the “Return Format” set to “Image Array”, and a Text field for the title.
Under “Settings”, set the field group’s location rules to show the field group if the block is set to “Slide.” Finally, click Save Changes to save your field group.
Template Logic
In this step, we’ll create the PHP templates that define how our slider and slide blocks will be rendered. These templates control both the editor preview and the front-end output of our blocks.
Slider Template
First, let’s create the template for our slider block. This file should be placed in the blocks/slider
directory.
<?php
/**
* Slider block.
*
* @param array $block The block settings and attributes.
* @param string $content The block inner HTML (empty).
* @param bool $is_preview True during backend preview render.
* @param int $post_id The post ID the block is rendering content against.
* This is either the post ID currently being displayed inside a query loop,
* or the post ID of the post hosting this block.
* @param array $context The context provided to the block by the post or it's parent block.
*/
// Support custom id values.
$block_id = wp_unique_prefixed_id( 'wpe-block-id-' );
if ( ! empty( $block['anchor'] ) ) {
$block_id = esc_attr( $block['anchor'] );
}
// Grab our alignment class.
$block_classes = '';
if ( '' !== $block['align'] ) {
$block_classes = 'align' . $block['align'];
}
// Which blocks do we want to allow to be nested in InnerBlocks.
$allowed_blocks = array(
'wpe/slide',
);
// Our InnerBlocks template to populate when new block is inserted.
$inner_blocks_template = array(
array(
'wpe/slide',
array(
'slideImg' => array(
'src' => '../slider/assets/image1.jpg',
),
'slideTitle' => 'Slide Title #1',
'className' => 'swiper-slide',
),
array(),
),
array(
'wpe/slide',
array(
'slideImg' => array(
'src' => '../slider/assets/image2.jpg',
),
'slideTitle' => 'Slide Title #2',
'className' => 'swiper-slide',
),
array(),
),
array(
'wpe/slide',
array(
'slideImg' => array(
'src' => '../slider/assets/image3.jpg',
),
'slideTitle' => 'Slide Title #3',
'className' => 'swiper-slide',
),
array(),
),
);
?>
<?php if ( ! $is_preview ) : ?>
<div
<?php
echo wp_kses_data(
get_block_wrapper_attributes(
array(
'id' => esc_attr( $block_id ),
'class' => $block_classes,
)
)
);
?>
>
<?php endif; ?>
<div class="swiper">
<InnerBlocks
allowedBlocks="<?php echo esc_attr( wp_json_encode( $allowed_blocks ) ); ?>"
class="swiper-wrapper wp-block-wpe-slider__innerblocks"
orientation="horizontal"
template="<?php echo esc_attr( wp_json_encode( $inner_blocks_template ) ); ?>"
/>
<?php if ( 'is-style-complex' === $block['className'] ) : ?>
<div class="swiper-pagination"></div>
<?php endif; ?>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div><!-- .swiper -->
<?php if ( ! $is_preview ) : ?>
</div>
<?php endif; ?>
Let’s break down this template:
- We generate a unique
id
for each slider instance, which is crucial for JavaScript initialization. - We create a
className
that includes ‘swiper’ (required for Swiper.js) and any additional classes from the block editor. - The main structure follows Swiper.js requirements: a main container, a wrapper for slides, and elements for pagination and navigation.
- We use
InnerBlocks
to allow for nested slide blocks. TheallowedBlocks
attribute ensures only our custom slide blocks can be inserted.
Slide Template
Now, let’s create the template for our slide block in the blocks/slide
directory.
<?php
/**
* Slide block.
*
* @param array $block The block settings and attributes.
* @param string $content The block inner HTML (empty).
* @param bool $is_preview True during backend preview render.
* @param int $post_id The post ID the block is rendering content against.
* This is either the post ID currently being displayed inside a query loop,
* or the post ID of the post hosting this block.
* @param array $context The context provided to the block by the post or it's parent block.
*/
$slide_img = get_field( 'image' );
$slide_title = get_field( 'title' ) ? get_field( 'title' ) : $block['slideTitle'];
if ( $slide_img['ID'] ) {
$slide_img = wp_get_attachment_image( $slide_img['ID'], 'full', '', array( 'class' => 'wp-block-wpe-slide__img' ) );
} else {
$slide_img = '<img src="' . plugins_url( $block['slideImg']['src'], __FILE__ ) . '" height="1080" width="1920" class="wp-block-wpe-slide__img">';
}
// Support custom id values.
$block_id = wp_unique_prefixed_id( 'slide-id-' );
if ( ! empty( $block['anchor'] ) ) {
$block_id = esc_attr( $block['anchor'] );
}
$block_classes = 'swiper-slide';
// Grab our alignment class.
if ( '' !== $block['align'] ) {
$block_classes = ' align' . $block['align'];
}
?>
<?php if ( ! $is_preview ) : ?>
<div
<?php
echo wp_kses_data(
get_block_wrapper_attributes(
array(
'id' => esc_attr( $block_id ),
'class' => $block_classes,
)
)
);
?>
>
<?php endif; ?>
<?php echo wp_kses_post( $slide_img ); ?>
<p class="wp-block-wpe-slide__copy"><?php echo esc_html( $slide_title ); ?></p>
<?php if ( ! $is_preview ) : ?>
</div>
<?php endif; ?>
Here’s what this template does:
- We retrieve the image and title data from ACF fields using
get_field()
. - Each slide is wrapped in a
swiper-slide
class, which is required by Swiper.js. - We use
wp_get_attachment_image()
to output the image. This function automatically generates responsivesrcset
attributes. - The title is output in a paragraph tag, with proper escaping for security.
These templates work together to create the structure of our slider:
- The slider template provides the overall container and allows for nested slides.
- Each slide template renders an individual slide with its image and title.
By separating the logic this way, we create a flexible system where users can easily add, remove, or reorder slides within the slider block. The use of ACF fields in the slide template allows for easy customization of each slide’s content.
Integrating Swiper.js
Swiper.js is a JavaScript library useful for creating modern touch sliders. In this section, we’ll integrate Swiper.js into our slider block to provide smooth, responsive sliding functionality.
First, we need to include Swiper.js in our plugin:
- Download Swiper.js from the official website or use a CDN.
- If downloaded, extract it and place the directory in your plugin, renaming it to
swiper
.
The required code to integrate Swiper.js has already been included in the snippets above. For more on exactly how to integrate Swiper.js in your project, please see this chapter in the video.
You can find the complete codebase for the final slider block here.