Create a WordPress Accordion without a Plugin

WordPress accordion tutorial

Create a lightweight JavaScript accordion for WordPress blocks without installing a plugin. This is an easy-to-follow tutorial and the code can be customised to fit into your projects.

The accordion can work in two modes:

  1. Classic accordion: Only one item (pane) is expanded at any time.
  2. Any number of items can be expanded at the same time.

The demo here is configured to run in “classic” mode.

Item one

This is regular Block Editor content. Lorem ipsum dolor sit amet.

Headwall WordPress Tutorials

Item two

Lorem ipsum dolor sit amet. Ullamco laborum Duis ad, non minim. Ex commodo, enim esse ut minim nisi. Proident ad dolore eiusmod. Officia eu enim.

Item three

Deserunt Duis labore nulla lorem, sed, in aliquip sed. Aliqua consequat, Ut mollit in velit lorem.

Project requirements

The core requirement is that each accordion pane can be created in the WP Block Editor. So we can use paragraphs, images, columns, etc to create content in the accordion panes. We also want the code to be lightweight (no external dependencies like jQuery), and it should be easy to use in multiple projects.

How it works

We’re going to control things by adding a CSS class to the blocks we want to use as accordion headers. Typically these will be “Heading” blocks (h2 or h3), but you can use any type of block you want.

When content loads in the front-end, our JavaScript will scan the page for elements with the wptac-header class. For each element we find, we’ll treat its next sibling element as an accordion “pane”. If you want to put two or more blocks in an accordion pane, you’ll need to wrap them in a Group Block. We’ll also assign a CSS class to the header’s parent element.

Using a CSS class to define an accordion control's header
Create accordions by adding a CSS class

The accordion will look something like this in the DOM…

Before

<div class="entry-content">
	<h2 class="wptac-header">Item one</h2>
	<p>Some text...</p>
	<h2 class="wptac-header">Item two</h2>
	<div class="wp-block-group">
		<p>Some text...</p>
		<p>Some text...</p>
		<p>Some text...</p>
	</div>
	<h2 class="wptac-header">Item three</h2>
	<p>Some text...</p>
</div>

After

<div class="entry-content wptac-container">
	<h2 class="wptac-header">Item one</h2>
	<p class="wptac-pane">Some text...</p>
	<h2 class="wptac-header">Item two</h2>
	<div class="wp-block-group wptac-pane">
		<p>Some text...</p>
		<p>Some text...</p>
		<p>Some text...</p>
	</div>
	<h2 class="wptac-header">Item three</h2>
	<p class="wptac-pane">Some text...</p>
</div>

We’ll set up JavaScript click event listeners on the header elements to make them clickable. When a header element is clicked, we’ll add the expanded class to it, and to its sibling element (the pane). Or… if the header is already expanded, we’ll remove the expanded class instead.

The animation will be handled by a CSS transition on the max-height property of the wptac-pane class. When a pane is collapsed max-height will be zero. When it’s expanded, it’ll be set to the natural scroll-height of the element instead.

The PHP Code

In your WordPress child theme, create three empty text files:

  • wpt-accordion.php
  • wpt-accordion.js
  • wpt-accordion.css

Open wpt-accordion.php and paste the following into it:

<?php

/**
 * Headwall WP Tutorials : Accordion (WPTACC)
 *
 * https://wp-tutorials.tech/refine-wordpress/wordpress-accordion-without-a-plugin/
 */

// Don't access this file directly.
defined('WPINC') || die();

// Use a Font Awesome 5 graphic for the up/down toggler graphic. You can set
// this to an empty string if you don't want to see a toggler graphic in the
// headers.
const WPTACC_TOGGLER_HTML = '<i class="fas fa-chevron-circle-down"></i>';

// Change the behaviour from classic accordion (one pane visible at a time) or
// multiple panes visible.
const WPTACC_COLLAPSE_OTHERS = true; // false;

/**
 * Enable/disable inclusion of the accordion assets in the page-load.
 * @return bool   return true if you want to include the assets on this page
 *                load, otherwise return false.
 */
function wptacc_is_accordion_available() {
	return is_singular() && !is_front_page();
}

/**
 * if wptacc_is_accordion_available() returns true, enqueue our accordion
 * assets in the front-end.
 */
function wptacc_enqueue_scripts() {
	if (wptacc_is_accordion_available()) {
		$theme_version = wp_get_theme()->get('Version');
		$base_uri = get_stylesheet_directory_uri();
		$handle = 'wptacc';

		wp_enqueue_style(
			$handle,
			$base_uri . '/wpt-accordion.css',
			null,
			$theme_version
		);

		wp_enqueue_script(
			$handle,
			$base_uri . '/wpt-accordion.js',
			null,
			$theme_version
		);

		// This is where we pass our runtime parameters to the front-end in a
		// global JavaScript object called "wptacData".
		wp_localize_script(
			$handle,
			'wptacData',
			array(
				'togglerHtml' => WPTACC_TOGGLER_HTML,
				'collapseOthers' => WPTACC_COLLAPSE_OTHERS,
			)
		);

		do_action('wpt_accordion_enqueued');
	}
}
add_action('wp_enqueue_scripts', 'wptacc_enqueue_scripts');

Then edit your child theme’s functions.php file and add the following couple of lines:

// WP Tutorials : Accordion
require_once dirname(__FILE__) . '/wpt-accordion.php';

All that the PHP code does is enqueue our CSS & JS assets, and create a global JavaScript object called wptacData to hold the runtime parameters.

You’ll probably want to change how wptacc_is_accordion_available() works. I’ve set it up so the accordion code is only available on URLs that are singular content (i.e. posts, pages, products, events), except for the front page. If you want the accordion code to be available on every URL, you can always return true from this function. If you only want the accordion code to work on WooCommerce product pages, you could do something like this:

function wptacc_is_accordion_available() {
	// Default behaviour : all singular content except the front page
	// return is_singular() && !is_front_page();

	// Modified behaviour : Only include accordion assets on WC Single Product pages
	return function_exists('is_product') && is_product();
}

The Frontend JavaScript

Open wpt-accordion.js and paste the following code into it:

/**
 * Headwall WP Tutorials : Accordion (WPTACC)
 *
 * wpt-accordion.js
 *
 * https://wp-tutorials.tech/refine-wordpress/wordpress-accordion-without-a-plugin/
 */

document.addEventListener('DOMContentLoaded', function() {
	'use strict';

	// Diagnostics
	// console.log('WPT Accordion : load');

	if (typeof wptacData !== 'undefined') {

		// Diagnostics
		// console.log('WPT Accordion : init');

		/**
		 * Initialse all accordion headers, panes and containers.
		 */
		wptacData.init = () => {
			// Loop through all ".wptac-header" elements and configure the
			// accordion panes.
			document.querySelectorAll('.wptac-header').forEach(function(accordionHeader) {
				const container = accordionHeader.parentElement;
				const pane = accordionHeader.nextElementSibling;

				// Make sure the header's parent element is set up as an
				// accordion container.
				if (!container.classList.contains('wptac-container')) {
					container.classList.add('wptac-container');
				}

				// Make sure the next element is NOT another header before we try
				// to turn it into a "pane".
				if (!pane.classList.contains('wptac-header')) {
					if (!pane.classList.contains('wptac-pane')) {
						pane.classList.add('wptac-pane');
					}

					// If the header has the "expanded" class, make sure the pane
					// starts as expanded too.
					if (accordionHeader.classList.contains('expanded')) {
						pane.classList.add('expanded');
						pane.style.maxHeight = pane.scrollHeight + "px";
					}

					// Create a small div to hold our toggler HTML
					// (if it's been passed in wptacData).
					if (wptacData.togglerHtml) {
						const toggler = document.createElement('div');
						toggler.classList.add('wptac-toggler');
						toggler.innerHTML = wptacData.togglerHtml;
						accordionHeader.append(toggler);
					}

					// Our main click event handler.
					accordionHeader.addEventListener('click', function(event) {
						wptacData.toggle(accordionHeader);
					});
				}

			});
		};

		/**
		 * Toggle the expanded/collapsed state of an accordion pane by passing
		 * the associated header element.
		 */
		wptacData.toggle = (accordionHeader) => {
			const container = accordionHeader.parentElement;
			const isExpanding = !accordionHeader.classList.contains('expanded');

			// Diagnostics
			// console.log( `Expanding: ${isExpanding}`);

			container.querySelectorAll('.wptac-header').forEach(function(testAccordionHeader) {
				const isThisTheTargetHeader = (testAccordionHeader == accordionHeader);
				const pane = testAccordionHeader.nextElementSibling;

				if (isThisTheTargetHeader && isExpanding) {
					testAccordionHeader.classList.add('expanded');
					pane.classList.add('expanded');
					pane.style.maxHeight = pane.scrollHeight + "px";
				} else if ((isThisTheTargetHeader && !isExpanding) || wptacData.collapseOthers) {
					testAccordionHeader.classList.remove('expanded');
					pane.classList.remove('expanded');
					pane.style.maxHeight = null;
				} else {
					// No change.
				}
			});
		};

		/**
		 * Main entry point
		 */
		wptacData.init();

	}
});

The code is commented and easy enough to read-through. The main thing to note is that we pass the runtime parameters into JS by calling wp_localize_script() to populate a global JS variable called wptacData. So if you want to change how the accordion code works, you can control it from the PHP code… you don’t need to edit the JavaScript for each project you deploy it on.

The Styles

The CSS for this project is straightforward. Just add the following to wpt-accordion.css and save it.

/**
 * Headwall WP Tutorials : Accordion (WPTACC)
 *
 * wpt-accordion.css
 * 
 * https://wp-tutorials.tech/refine-wordpress/wordpress-accordion-without-a-plugin/
 */

.wptac-container {
	/* You can add CSS for the accorion container here */
}

.wptac-header {
	cursor: pointer;
	position: relative;
}

.wptac-toggler {
	position: absolute;
	right: 10px;
	top: 50%;
	transform: translateY(-50%);
	transition: 0.4s transform;
}

.wptac-header.expanded .wptac-toggler {
	transform: translateY(-50%) rotate(180deg);
}

.wptac-pane {
	overflow: hidden;
	max-height: 0;
	min-height: unset !important;
	transition: 0.4s max-height;
}

Set up an accordion and test it

Edit some content and create a sequence of blocks like this:

  • Heading (Additional CSS CLASS = “wptac-header expanded”)
  • Paragraph
  • Heading (Additional CSS CLASS = “wptac-header”)
  • Paragraph
  • Heading (Additional CSS CLASS = “wptac-header”)
  • Paragraph

Save the content and view it in the front-end. You should have a working accordion 👍

You can replace the Paragraph blocks with any type of block… Columns, Group, etc. Remember that the containing block (parent block) will end up with the “wptac-container” class, so if you want multiple independent accordions on a page, you should wrap each one in a Group Block (or a Column Block).

Experiment with it and have fun 😎 👍

Like This Tutorial?

Let us know

WordPress plugins for developers

1 thought on “Create a WordPress Accordion without a Plugin”

Leave a comment