In this tutorial, we are going to Create an Automatic Table of Contents in WordPress without a plugin.

Check out LORENZ article on How to create a Table of Contents in WordPress (without plugin, my free code) (webdeasy.de) for a full breakdown.

Why use Table of Contents in WordPress?

  1. The main reason to use Table of Contents in WordPress is enhance User Experience. Some of the readers want to quickly read a specific section of your post, which may be covered in the lower part of the content. Scrolling down might be annoying so creating a Table of Contents helps to quickly reach the desired part of the content.
  1. Another reason for using a Table of Contents is that Search engines Shows Jump to links in Search Engine Results Pages (SERPs) so that users can directly click on the link and read the article.
  1. Furthermore, they can also show multiple Jump to links in SERPs so using TOC on your website can help users to reach the desired area of the content. Like the Post? Read it here: Design Styles.
Blog post Search Engine Results Pages
Blog post Search Engine Results Pages

it takes too long creating table of content for each Post. Why not create an automatic TOC and focus on righting your post.

Steps to Create the Automatic Table of Contents.

The key is to complete your article as you normally would. Write your blog post using heading h1 and subheadings h2, h3, etc. and the table of contents appears automatically after the first paragraph.

This requires a custom function. Right the code below in the functions.php, preferably in a child theme.

  • Go to Appearance > Theme File Editor > Theme Functions
  • Copy and paste the code below.

1. Generate the Table of content

function get_toc($content) {
  // get headlines
  $headings = get_headings($content, 1, 6);

  // parse toc
  ob_start();
  echo "<div class='table-of-contents'>";
  echo "<span class='toc-headline'>Table Of Contents</span>";
  echo "<span class='toggle-toc custom-setting' title='collapse'>−</span>";
  parse_toc($headings, 0, 0);
  echo "</div>";
  return ob_get_clean();
}

2. Finding and setting the heading in the page:

function parse_toc($headings, $index, $recursive_counter) {
  // prevent errors

  if($recursive_counter > 60 || !count($headings)) return;

  // get all needed elements
  $last_element = $index > 0 ? $headings[$index - 1] : NULL;
  $current_element = $headings[$index];
  $next_element = NULL;
  if($index < count($headings) && isset($headings[$index + 1])) {
    $next_element = $headings[$index + 1];
  }

  // end recursive calls
  if($current_element == NULL) return;

  // get all needed variables
  $tag = intval($headings[$index]["tag"]);
  $id = $headings[$index]["id"];
  $classes = isset($headings[$index]["classes"]) ? $headings[$index]["classes"] : array();
  $name = $headings[$index]["name"];

  // element not in toc
  if(isset($current_element["classes"]) && $current_element["classes"] && in_array("nitoc", $current_element["classes"])) {
    parse_toc($headings, $index + 1, $recursive_counter + 1);
    return;
  }

  // parse toc begin or toc subpart begin
  if($last_element == NULL) echo "<ul>";
  if($last_element != NULL && $last_element["tag"] < $tag) {
    for($i = 0; $i < $tag - $last_element["tag"]; $i++) {
      echo "<ul>";
    }
  }

  // build li class
  $li_classes = "";
  if(isset($current_element["classes"]) && $current_element["classes"] && in_array("toc-bold", $current_element["classes"])) $li_classes = " class='bold'";

  // parse line begin
  echo "<li" . $li_classes .">";

  // only parse name, when li is not bold
  if(isset($current_element["classes"]) && $current_element["classes"] && in_array("toc-bold", $current_element["classes"])) {
    echo $name;
  } else {
    echo "<a href='#" . $id . "'>" . $name . "</a>";
  }
  if($next_element && intval($next_element["tag"]) > $tag) {
    parse_toc($headings, $index + 1, $recursive_counter + 1);
  }

  // parse line end
  echo "</li>";

  // parse next line
  if($next_element && intval($next_element["tag"]) == $tag) {
    parse_toc($headings, $index + 1, $recursive_counter + 1);
  }

  // parse toc end or toc subpart end
  if ($next_element == NULL || ($next_element && $next_element["tag"] < $tag)) {
    echo "</ul>";
    if ($next_element && $tag - intval($next_element["tag"]) >= 2) {
      echo "</li>";
      for($i = 1; $i < $tag - intval($next_element["tag"]); $i++) {
        echo "</ul>";
      }
    }
  }

  // parse top subpart
  if($next_element != NULL && $next_element["tag"] < $tag) {
    parse_toc($headings, $index + 1, $recursive_counter + 1);
  }
}

3. Getting the available extracted Headings

function get_headings($content, $from_tag = 1, $to_tag = 6) {
  $headings = array();
  preg_match_all("/<h([" . $from_tag . "-" . $to_tag . "])([^<]*)>(.*)<\/h[" . $from_tag . "-" . $to_tag . "]>/", $content, $matches);
  
  for($i = 0; $i < count($matches[1]); $i++) {
    $headings[$i]["tag"] = $matches[1][$i];
    // get id
    $att_string = $matches[2][$i];
    preg_match("/id=\"([^\"]*)\"/", $att_string , $id_matches);
    $headings[$i]["id"] = $id_matches[1];
    // get classes
    $att_string = $matches[2][$i];
    preg_match_all("/class=\"([^\"]*)\"/", $att_string , $class_matches);
    for($j = 0; $j < count($class_matches[1]); $j++) {
      $headings[$i]["classes"] = explode(" ", $class_matches[1][$j]);
    }
    $headings[$i]["name"] = strip_tags($matches[3][$i]);
  }
  return $headings;
}

4. Automatically inserted the table of contents after the first paragraph


function add_table_of_content($content) {
  if (!is_single()) return $content;
  
    $paragraphs = explode("</p>", $content);
    $paragraphs_count = count($paragraphs);
    $middle_index= absint(floor($paragraphs_count / 2));
    $new_content = '';

    for ($i = 0; $i < $paragraphs_count; $i++) {
        if ($i === 1) {
          $new_content .= get_toc($content);
        }

        $new_content .= $paragraphs[$i] . "</p>";
    }
    return $new_content;
}
add_filter('the_content', 'add_table_of_content');

5. Automatically add IDs to headings such as <h2></h2>

function auto_id_headings( $content ) {
  $content = preg_replace_callback('/(\<h[1-6](.*?))\>(.*)(<\/h[1-6]>)/i', function( $matches ) {
    if(!stripos($matches[0], 'id=')) {
      $matches[0] = $matches[1] . $matches[2] . ' id="' . sanitize_title( $matches[3] ) . '">' . $matches[3] . $matches[4];
    }
    return $matches[0];
  }, $content);
    return $content;

}
add_filter('the_content', 'auto_id_headings');

6. Styling the Table of Contents

  • Go to Appearance > Customize > Additional CSS.
  • Copy and paste the code.

Now, let’s style our Table of Contents using CSS code. Feel free to tweak the CSS code according to your theme.

:root {
  --dark-grey: #333333;
  --background-color: #eee;
  --main-color: #d4b068;
  --font-size: 16px;
  --line-height: 1.2;
}

.table-of-contents {
  margin: 4rem 0;
  position: relative;
  background-color: var(--background-color);
  padding: .5rem 1rem;
  border-left: 5px solid var(--main-color);
  border-radius: 20px;
	box-shadow: 0px 0px 20px -10px rgb(0 0 0 / 80%);
	border: 1px solid #ccc;
}

.table-of-contents .toc-headline {
  font-size: 22px;
  color: var(--dark-grey);
  font-weight: 600;
  display: block;
  cursor: pointer;
  margin-top: .3rem;
}

.table-of-contents .toggle-toc {
  position: absolute;
  top: .8rem;
  right: .8rem;
  font-size: 20px;
  cursor: pointer;
  font-weight: 800;
  color: #FFF;
  width: 1.5rem;
  height: 1.5rem;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 50%;
  line-height: 10px;
  background-color: var(--main-color);
}

.table-of-contents ul {
  padding: 0;
}

.table-of-contents li {
  position: relative;
  list-style: none;
  line-height: var(--line-height);
  font-weight: 400;
  margin: .3rem 0;
  transition: .2s ease all;
}

.table-of-contents li a {
  font-size: var(--font-size);
  line-height: var(--line-height);
  color: var(--main-color);
}

.table-of-contents li>ul {
  padding-left: 1rem;
  padding-bottom: .5rem;
}

How to add Smooth Scroll code?

Result

The Table of Contents looks like this.

Automatic Table of Content
Automatic Table of Content

About Brian Gathu

mmGathu is a business and tech geek who spends half of his day, branding and designing. While the nights are spent on writing. You can check out-