Display Taxonomy Terms in an Indexed List

Taxonomy term list shortcode tutorial

List taxonomy terms in the style of an index, with permalinks to each of the terms. It’s a customisable and easy-to-use shortcode. Handy for use in sidebars, or as part of a dedicated index page.

This is a simple tutorial, comprising a PHP file for the core logic and CSS file to hold the styles. So let’s begin by scaffolding the project.

Scaffold the code

Start by creating a file in your child theme called wpt-taxonomy-term-index.php and add the following to it:

<?php

/**
 * Headwall WP Tutorials Taxonomy Term Index (WPTTTI)
 *
 * https://wp-tutorials.tech/add-functionality/display-taxonomy-terms-in-an-indexed-list/
 */

// Block direct access.
defined('WPINC') || die();

function wpttti_do_shortcode_term_index($atts) {
	$html = '';

	if (is_admin() || wp_doing_ajax()) {
		// Do nothing.
	} else {
		// Create the HTML in here...
		// ...
		// ...
		$html .= '<p>TERMS IN HERE</p>';
	}

	return $html;
}
add_shortcode('taxonomy_term_list', 'wpttti_do_shortcode_term_index');

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

// Headwall WP Tutorials : Taxonomy Term Index
require_once dirname(__FILE__) . '/wpt-taxonomy-term-index.php';

Save those, then edit a post (or a page, or any type of content) and add a shortcode so we’ve got a place to test things.

Taxonomy term list shortcode
Display all post-tag terms in two column list

Publish your post and then view it. You should see “TERMS IN HERE” where you’ve added the shortcode.

Create the main function

The logic for the main function works like this:

  1. Parse the parameters to the shortcode:
    • taxonomy (default=”category”)
    • cols (default=1)
    • headling_level (default=3, The HTML tag for the letters before each group of terms, e.g. <h3>)
  2. Enqueue the stylesheet
  3. Open a <div> wrapper
    • Add a CSS class based on the number of columns we need
  4. Call get_terms() to get an array of terms in our taxonomy
    • Use the $tax_query_args array to control which terms get_terms() returns. If you want to customise the list of terms in more detail, have a look at the documentation for WP_Term_Query::__construct()
  5. For each term…
    • Get the first letter of the term, e.g. “A”
    • If the first letter is different from the first letter of the previous term (or if this is the first time around the loop), then…
      • Render the first letter wrapped in <h3> (heading_level)
      • Open an unordered list <ul>
    • Render the term’s text and permalink as a list-item <li>
  6. Close the wrapper with </div>

Replace the wpttti_do_shortcode_term_index() function with the following code:

function wpttti_do_shortcode_term_index($atts) {
	$html = '';

	if (is_admin() || wp_doing_ajax()) {
		// Do nothing.
	} else {
		// Enqueue the front-end stylesheet.
		global $wpttti_have_assets_been_enqueued;
		if (is_null($wpttti_have_assets_been_enqueued)) {
			$theme_version = wp_get_theme()->get('Version');
			$base_uri = get_stylesheet_directory_uri();

			wp_enqueue_style(
				'wpttti-term-index',
				$base_uri . '/wpt-taxonomy-term-index.css',
				null,
				$theme_version
			);

			$wpttti_have_assets_been_enqueued = true;
		}

		// Merge the shortcode's parameters with sensible defaults.
		$args = shortcode_atts(
			array(
				'heading_level' => 3,
				'taxonomy' => 'category',
				'number' => 0, // Get all terms
				'hide_empty' => true,
				'cols' => 1,
			),
			$atts
		);

		// Sanitise the shortcode's parameters.
		$args['heading_level'] = intval($args['heading_level']);
		$args['hide_empty'] = filter_var($args['hide_empty'], FILTER_VALIDATE_BOOLEAN);
		$args['cols'] = intval($args['cols']);

		$wrapper_classes = array(
			'taxonomy-term-index',
			'term-index-cols-' . $args['cols'],
		);

		if (empty($args['taxonomy'])) {
			$html .= '<p>ERROR: No taxonomy specified in the shortcode</p>';
		} elseif (!taxonomy_exists($args['taxonomy'])) {
			$html .= sprintf('<p>ERROR: Taxonomy not found, <strong>%s</strong></p>', $args['taxonomy']);
		} else {
			$tax_query_args = array(
				'taxonomy' => $args['taxonomy'],
				'hide_empty' => $args['hide_empty'],
				'number' => $args['number'],
				'orderby' => 'name',
				'order' => 'ASC',
			);

			$terms = get_terms($tax_query_args);
			$previous_first_letter = '';
			$letter_index = 0;
			if (is_array($terms) && (count($terms) > 0)) {
				// Open the wrapper container.
				$html .= sprintf(
					'<div class="%s">',
					esc_attr(implode(' ', $wrapper_classes))
				);

				foreach ($terms as $term) {
					$first_letter = strtoupper(substr($term->name, 0, 1));

					if ($first_letter != $previous_first_letter) {
						// Only close the previous term-letter if this is not
						// the first letter we've rendered.
						if ($letter_index > 0) {
							$html .= '</ul>';
							$html .= '</div>'; // .taxonomy-term-letter
						}

						// Open a wrapper for this term letter's heading and the
						// list of terms.
						$html .= '<div class="taxonomy-term-letter">';

						$html .= sprintf(
							'<h%d>%s</h%d>',
							$args['heading_level'],
							esc_html($first_letter),
							$args['heading_level'],
						);

						// Open this term-letter's unordered list.
						$html .= '<ul>';

						++$letter_index;
						$previous_first_letter = $first_letter;
					}

					// Our term's list item.
					$html .= sprintf(
						'<li><a href="%s">%s</a></li>',
						esc_url(get_term_link($term)),
						esc_html($term->name)
					);
				}

				// Close the term-letter's list and wrapper'
				$html .= '</ul>';
				$html .= '</div>'; // .taxonomy-term-letter

				// Close the outer wrapper container.
				$html .= '</div>'; // .taxonomy-term-index
			}
		}
	}

	return $html;
}

Then all we need to do is add some style definitions. Add the following to wpt-taxonomy-term-index.css to get started:

/**
 * wpt-taxonomy-term-index.css
 *
 * https://wp-tutorials.tech/add-functionality/display-taxonomy-terms-in-an-indexed-list/
 */

.taxonomy-term-index {
	/* Custom wrapper styles in here */
}

/**
 * Only layout in columns past the 922px breakpoint.
 */
@media(min-width: 922px) {
	.taxonomy-term-index.term-index-cols-2,
	.taxonomy-term-index.term-index-cols-3,
	.taxonomy-term-index.term-index-cols-4 {
		display: flex;
		flex-direction: row;
		flex-wrap: wrap;
		justify-content: space-between;
	}

	.taxonomy-term-index.term-index-cols-2 .taxonomy-term-letter {
		width: 48%;
	}

	.taxonomy-term-index.term-index-cols-3 .taxonomy-term-letter {
		width: 31%;
	}

	.taxonomy-term-index.term-index-cols-4 .taxonomy-term-letter {
		width: 23%;
	}
}

.taxonomy-term-letter h3 {
	margin-bottom: 0;
	border-bottom: 1px dotted grey;
	font-size: 16pt;
}

.taxonomy-term-index ul {
	list-style-type: none;
	padding: 0;
	margin: 0 0 1em 0;
}

Listing WooCommerce attribute terms

You can use this shortcode to show attribute terms in a tidy list. This is neat if you’ve got a product brand attribute and you want to see all your brands, sorted alphabetically. When you’ve got a product attribute (e.g. “Brand”), find its slug by hovering your mouse over “Configure terms”:

WooCommerce Brand Product Attributes
The “Brand” attribute is really the “pa_brand” taxonomy

You’ll see that the URL looks something like this:

/wp-admin/edit-tags.php?taxonomy=pa_brand&post_type=product

In this case the taxonomy is “pa_brand”. You’ll see that all WooCommerce attributes are just regular taxonomies, prefixed by “pa_” (for “product attribute”).

Wrapping up

The PHP functions renders our shortcode is just a call to a core WordPress function and then loops around the array of results to build an HTML string. If you want to customise the contents of the list of terms, manipulate $tax_query_args, and if you want to adjust the presentation, make changes in wpt-taxonomy-term-index.css. Have fun 😎👍

Like This Tutorial?

Let us know

WordPress plugins for developers

Leave a comment