Building a WordPress Accordion Block, No JavaScript Required

Advanced Custom Fields and Bootstrap Logos

In this post we’ll go over how to build a Bootstrap accordion as a WordPress block using Advanced Custom Fields. This is a useful approach for rapidly building WordPress blocks. It’s also a great stopgap for developers who are still learning JavaScript, since it doesn’t require you to write any JavaScript.

Just looking for the code? Check out the GitHub Repo.

Quick Links

Prerequisites (assumptions)

This tutorial assumes a basic understanding of how WordPress themes are structured. In particular, you should be familiar with what functions.php is in the context of a theme, the basics of how to use action hooks, and how templates work in WordPress Themes. If you’ve worked with child themes before or built a WordPress theme, you’ll likely be familiar with functions used in this tutorial. If you haven’t, we’ll cover how to set up a simple child theme.

ACF functions used in this tutorial

Background knowledge of Bootstrap

Additionally, having a basic understanding of Bootstrap (what it is, why it’s useful as a ‘shortcut’) will help. While we won’t use the Bootstrap grid, we will be implementing a version of the Bootstrap collapse component as well as the Bootstrap card component. You don’t need to have used these before to follow along, especially if you’ve used other Bootstrap components before.

Getting Started

To follow along, you’ll need a simple WordPress website that you can hack on. For simplicity, we’ll work inside a child theme. We’ll extend TwentyTwenty, a default theme which comes with every WordPress installation, though any theme will do. If you’re building just for yourself, this is adequate. If you’re building blocks you want to distribute, it’s better to keep them in a plugin. That way, switching themes won’t remove content from posts and pages.

Create your child theme

Begin by creating a new directory in the themes directory. The best practice would be to name it twentytwenty-child, but you can pick whatever name you’d like. Inside this directory, create a style.css file and add the following header:

/*  Theme Name:   Twenty Twenty Child  Author:       [your name]  Template:     twentytwenty */
Code language: PHP (php)

The most important part is to specify the template, which tells WordPress which theme is this one’s parent. You can add additional information, such as the theme version or a description, to your header as documented in the developer handbook. For reference, here’s the header from mine:

/*  Theme Name:   Accordion Tutorial  Description:  Twenty Twenty child theme for demoing a Bootstrap accordion block.  Author:  Jack Lowrie  Author URI:  Template:  twentytwenty  Version: 1.0.0  License: GNU General Public License v2 or later  License URI: */
Code language: PHP (php)

Then, create a functions.php file, and add the following to it:

<?php function enqueue_parent_styles() {    wp_enqueue_style( 'parent-style', get_template_directory_uri().'/style.css' ); } add_action( 'wp_enqueue_scripts', 'enqueue_parent_styles' );
Code language: PHP (php)

This enqueues TwentyTwenty’s styles before ours, that way the changes we make will extend TwentyTwenty rather than replace them. And that’s it for now! You have a child theme ready to hack on.

Enqueue Bootstrap

Of course, we can’t build anything with Bootstrap without enqueueing it! To keep it simple, we’ll load it in from Bootstrap’s CDN. add the following to your theme’s functions.php file:

function enqueue_bootstrap() { wp_enqueue_style('bootstrap-styles', ''); wp_enqueue_style('bootstrap-styles', ''); wp_enqueue_script( 'bootstrap-scripts','', array( 'jquery' ), '', true ); } add_action( 'wp_enqueue_scripts', 'enqueue_bootstrap' );
Code language: PHP (php)

This code ensures that Bootstrap (and it’s dependencies) will load on your site.

Install Advanced Custom Fields Pro (ACF)

You’ll also need to have the Advanced Custom Fields plugin installed. While the core plugin is free and available in the WordPress Plugin Directory, it doesn’t give you all the capabilities and field types available with a Pro license. This tutorial makes use of the repeater field and the block-level field group, both of which require a pro license. ACF Pro is potent, and is worth the investment. Even if you don’t need to build custom blocks, you may find yourself reaching for it on more WordPress projects than not.

Registering your first block

Once you have a WordPress site (with Bootstrap styles and scripts enqueued and ACF Pro activated), you’re ready to start building blocks. The bird’s eye view for this is to create a template file for your block, then register it using ACF’s block registration function to ensure that the block is only registered if ACF is present on the site.

It’s helpful to keep all block templates in a folder to stay organized. Where you keep it is up to you, especially if you’re doing this in a theme and not a plugin — it may make sense to keep it as a top-level folder in the theme or to place it inside of a templates or template-parts folder if your theme has one. We’ll keep ours top-level for now. Create a new blocks folder in the root of your theme, then create a new template file called accordion.php inside. We don’t need to add anything to this file yet. It only needs to exist.

Then, we need to register that file (in functions.php, just like we enqueued Bootstrap).

function register_acf_block_types() { acf_register_block_type(array( 'name' => 'accordion', 'title' => __('Accordion'), 'category' => 'custom', 'render_template' => 'blocks/accordion.php', )); } add_action( 'acf/init', 'register_acf_block_types' );
Code language: PHP (php)

You’re done! Sort of.

This is actually all there is to registering a block. If you add a block to a post or page now, the accordion will show up as an option. If you use it, any markup inside that template file will show up in the corresponding spot on the post or page. In fact, let’s do that! We can add the accordion example from the bootstrap collapse docs to a page as proof that we have a movable, renderable block (this is also an easy way to verify that we enqueued Bootstrap correctly). 

Now, this isn’t a terribly useful block (it’s entirely uneditable, and we’re dependent on ACF to register the block even though we haven’t made any custom fields). But let’s not downplay the accomplishment! 

FWIW, ACF’s register block function at its core is a php wrapper for WordPress’s Javascript function RegisterBlockType(), which is how we’d register the block if we didn’t use ACF. We are avoiding that since this post is about building blocks without Javascript.

Setting up a field group

Now that we have a block template, we need to set up fields to render in that block. We can set field groups to display only on specific blocks! From the Custom Fields menu in the WordPress Dashboard, add a new field group and call it Block - Accordion. It’s nice to keep all the field groups for blocks together in the list, though this is up to personal preference. Then add a location rule: “show this field group if block is equal to Accordion

An accordion is a perfect candidate for a repeater field in ACF. Each new row in the repeater will correspond to a new ‘fold’ in the accordion, and all the folds will have the same subfields and layout.

So, add a repeater field and call it ‘folds’, then for simplicity of this demo, add a text field called ‘title’ and a WYSIWYG subfield called ‘content’. Now, if we add our accordion block to any page, we’ll be able to add as few or as many folds to the accordion as we’d like, and the content of each fold is customizable with a WYSIWYG editor! 

We can save this data, and it will persist on the individual instance of the block, though we still need to render it in the template.

Rendering field content in the block template

Now, we can start to render each fold in the template. Each .card element in the example code we pasted in earlier makes up one fold. So first, let’s remove all but one ‘fold’, by deleting all the divs after the first card, taking care not to remove the closing tag for the accordion, and also remove all the placeholder content:

<div class="card">   <div class="card-header" id="headingOne">     <h2 class="mb-0">       <button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">              </button>     </h2>   </div>   <div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordionExample">     <div class="card-body">            </div>   </div> </div>
Code language: HTML, XML (xml)

This is our fold template, which we can fill with the content from our block’s custom fields. Now, we can iterate over all the folds from the repeater and render them as a new .card. To do this, we’ll use two ACF functions, have_rows() and the_row(). These are used almost identically to have_posts() and the_post(), so if you’ve ever seen a WordPress loop, this might be a familiar pattern! Wrap our fold template in a while loop, then grab the fold content using get_sub_field():

// Loop through rows. <?php  while ( have_rows( 'folds' ) ) : the_row();     $fold_title = get_sub_field( 'title' );     $fold_content = get_sub_field( 'content' ); ?> <div class="card">    <div class="card-header" id="headingOne">      <h2 class="mb-0">        <button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">        <?php echo $fold_title; ?>        </button>      </h2>    </div>    <div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordionExample">      <div class="card-body">        <?php echo fold_content; ?>     </div>    </div> </div> <?php endwhile; ?>
Code language: HTML, XML (xml)

Our accordion is almost good to go! If we look at this on a page now, we’d see each fold we added to the block rendered, but the accordion wouldn’t behave the way we want. This is because the Bootstrap accordion counts on each fold having a unique id. In our code, every fold has an id of collapseOne. We can follow a similar convention using ACF’s get_row_index() function, which will tell us which iteration of the while loop we’re currently in (by telling us which row we’re currently rendering). We can also update the aria labels at the same time:

<?php  while ( have_rows( 'folds' ) ) : the_row();     $fold_title = get_sub_field( 'title' );     $fold_content = get_sub_field( 'content' ); ?> <div class="card">   <div class="card-header" id="heading-<?php echo get_row_index(); ?>">     <h2 class="mb-0">       <button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapse-<?php echo get_row_index(); ?>" aria-expanded="true" aria-controls="collapse-<?php echo get_row_index(); ?>">       <?php echo $fold_title; ?>       </button>     </h2>   </div>   <div id="collapse-<?php echo get_row_index(); ?>" class="collapse show" aria-labelledby="heading-<?php echo get_row_index(); ?>" data-parent="#accordionExample">     <div class="card-body">       <?php echo fold_content; ?>     </div>   </div> </div> <?php endwhile; ?>
Code language: PHP (php)

Now, our accordion works! The last thing to do, since this is now a fully-functioning accordion and not just an example, is to update the id of the accordion (and the data-parent attribute of each fold) from #accordionExample to #accordion. Not technically necessary, but names matter!

That’s it!

We now have a fully functional accordion block. We can use this as a template to make other Bootstrap components as well, including Jumbotrons, Media Objects, List Groups, and more.

You can use the code from this tutorial as a template for creating other Bootstrap blocks for practice or to fit your needs. See the full code in my github repo: jacklowrie/wp-accordion-tutorial.

Get in Touch!