Auto-Numbered Headings

Numbered headings tutorial for WordPress

In this WordPress tutorial, we’ll create some code to automatically number the headings in any post content. They can be ascending or descending and start at any number. It’s great for Top Ten list articles!

We’ll do most of the work in the browser using JavaScript, so it will work with page builders like Elementor, as well as the built-in Block Editor. Just make sure you’re using a custom child theme so you can edit “functions.php”.

infoThis is a great tutorial for anyone starting to learn PHP and JavaScript for WordPress.

We’re Going to Make This

Heading One

Officia non anim culpa, ut esse. Velit cillum pariatur velit.

Heading Two

Eu cillum ullamco tempor, enim eiusmod.

Heading Three

Velit ad sint. Consequat ipsum mollit eiusmod cupidatat, deserunt.

Heading Four

Laborum ullamco veniam tempor sed. Do anim Excepteur non.

Preparation

The logic we want to implement in the browser breaks down like this…

  1. Set a variable to hold the current “heading number”, usually to a value of “1”, but it could be anything.
  2. Find all elements on the page that match a query selector, e.g. “.entry-content h2”
  3. For each element that matches the query selector…
    1. Create a span element to hold the heading number
    2. Set the span’s innerText property to the current heading number
    3. Add a CSS class to the span so we can style it
    4. Insert the span’s HTML into the heading element, before the text that’s already in there
    5. Add +1 (ascending) or -1 (descending) to the current heading number

All the back-end (PHP) code needs to do is enqueue our JavaScript and CSS assets. But… we don’t want to include our JS code on every single page-load… we only want to enqueue it when it’s needed. For the most part, we can use WordPress’ Conditional Tags like is_archive() and is_front_page(), but we can also do some neat stuff with a custom field (custom post meta).

Custom Field

Install the Advanced Custom Fields plugin, if it’s not already on your site.

If you’ve already got a field group associated with your content type (e.g. “Post”) then you can add the new field to it. Otherwise, you’ll need to add a new field group now.

Edit your field group and add a new True / False field. The Field Label can be anything you want, but the Field Name must be “_auto_numbered_headings” – we’re going to reference it in the code.

Save your field group and then edit a post where you want to auto-number the headings. Enable the new toggle switch and save the change.

Enable auto-numbered headings
Enable auto-numbering at post-level
Auto numbered headings ACF field
Create an ACF field to enable numbered headings

Write the Code

In your custom child theme, create a new file called “wpt-numbered-headings.php”. Then create a folder called “wpt-numbered-headings” and add two empty text files to it, called “numbered-headings.js” and “numbered-headings.css”

Edit wpt-numbered-headings.php and paste the following into it

<?php

/**
 * WP Tutorials : Auto-Numbered Headings (WPTNH)
 *
 * https://wp-tutorials.tech/refine-wordpress/auto-numbered-headings/
 */

defined('WPINC') || die();

const WPTNH_HEADING_SELECTOR = '.entry-content h2';
const WPTNH_HEADING_START = 10;
const WPTNH_HEADING_STEP = -1;

const WPTNH_ENABLED_FIELD_NAME = '_auto_numbered_headings';

const WPTNH_USE_ON_ARCHIVES = false;
const WPTNH_USE_ON_FRONT_PAGE = false;
const WPTNH_USE_ON_BLOG = false;
const WPTNH_USE_ON_ALL_POSTS = true;

function wptnh_enqueue_scripts() {
	$enable_numbered_headings = false;

	$enable_numbered_headings |= WPTNH_USE_ON_ARCHIVES && is_archive();
	$enable_numbered_headings |= WPTNH_USE_ON_FRONT_PAGE && is_front_page();
	$enable_numbered_headings |= WPTNH_USE_ON_BLOG && is_home();

	if (WPTNH_USE_ON_ALL_POSTS) {
		$enable_numbered_headings |= is_single();
	} else {
		$enable_numbered_headings |= filter_var(get_post_meta(get_the_ID(), WPTNH_ENABLED_FIELD_NAME, true), FILTER_VALIDATE_BOOLEAN);
	}

	if ($enable_numbered_headings) {
		$theme_version = wp_get_theme()->get('Version');
		$base_uri = get_stylesheet_directory_uri();
		$handle = 'wptnh';

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

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

		// Pass some data to the front-end in a global JavaScript variable
		// called wptnhData
		wp_localize_script(
			$handle,
			'wptnhData',
			array(
				'headingSelector' => WPTNH_HEADING_SELECTOR,
				'numbering' => array(
					'start' => WPTNH_HEADING_START,
					'step' => WPTNH_HEADING_STEP,
				),
			)
		);
	}
}
add_action('wp_enqueue_scripts', 'wptnh_enqueue_scripts');

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

// Headwall WP Tutorials Auto-numbered headings
require_once dirname(__FILE__) . '/wpt-numbered-headings.php';

That’s the back-end code sorted out. Read through the comments and make sure you know what wp_localize_script() is doing – it’s the glue that links the back-end code to the JavaScript code in the browser.

To get started on the front-end stuff, edit “wpt-numbered-headings/numbered-headings.js” and paste this into it:

/**
 * WP Tutorials : Auto-Numbered Headings (WPTNH)
 *
 * https://wp-tutorials.tech/refine-wordpress/auto-numbered-headings/
 */
document.addEventListener('DOMContentLoaded', function() {
	'use strict';

	// Uncomment this for diagnostics
	// console.log('WPT Numbered Headings : load');

	if (typeof wptnhData !== 'undefined') {
		// Uncomment this for diagnostics
		// console.log(`WPT Numbered Headings : init`);
		// console.log(`Selector : ${wptnhData.headingSelector}`);

		let currentHeadingNumber = wptnhData.numbering.start;

		const headingElements = document.querySelectorAll(wptnhData.headingSelector).forEach(function(headingElement) {
			// Uncomment this for diagnostics
			// console.log(`Found heading : ${headingElement.innerText}`);

			// Create a span element and set its text and CSS class. We're not
			// actually going to add this element to the DOM, we're going to use
			// the outerHTML property to "render" the element.
			const numberElement = document.createElement('span');
			numberElement.innerText = currentHeadingNumber;
			numberElement.classList.add('heading-number');

			// Insert the HTML representation of the span element to the
			// beginning of the heading.
			headingElement.innerHTML = `${numberElement.outerHTML}${headingElement.innerHTML}`;
			headingElement.classList.add('numbered-heading');

			currentHeadingNumber += wptnhData.numbering.step;
		});
	}

});

Finally, a bit of style for “wpt-numbered-headings/numbered-headings.css“:

/**
 * WP Tutorials : Auto-Numbered Headings (WPTNH)
 *
 * https://wp-tutorials.tech/refine-wordpress/auto-numbered-headings/
 */

.numbered-heading {
	border-bottom: 1px solid black;
}

.heading-number {
	background-color: black;
	color: white;
	display: inline-block;
	width: 3em;
	text-align: center;
	margin-right: 0.5em;
	padding: 0.3em 0;
}

Deploy, Test & Extend

If the code doesn’t seem to be working for you, uncomment the diagnostics lines in the JavaScript file and reload the content. When you open the Dev Tools JS Console, you should see confirmation that the script has loaded, initialised and found some headings. If it’s not finding any headings, you probably just need to change the element selector. Try setting WPTNHG_HEADING_SELECTOR to something simpler like “h2”, at the top of the PHP file.

To change the numbering so it starts at 1 and counts upwards, change WPTNH_HEADING_START to a value of 1 and WPTNH_HEADING_STEP to a value of 1, like this:

const WPTNH_HEADING_START = 1; // 10;
const WPTNH_HEADING_STEP = 1; // -1;

That’s it, really. A simple little module that doesn’t try and do more than it needs to do… keep it simple and elegant.

Happy auto-numbering 😎 👍

Like This Tutorial?

loading

Let us know

WordPress plugins for developers

Leave a comment