Smart Table of Contents (TOC) Shortcode with Scroll Tracking

Why it’s new and useful:
Table of Contents plugins are everywhere, but most are heavy or lack dynamic features. This snippet introduces a [smart_toc] shortcode that auto-generates a Table of Contents from your post’s headings (H2/H3), highlights the current section as you scroll, and is lightweight—no external dependencies!

1. Add the Snippet to Your Theme’s functions.php or a Custom Plugin

// Smart TOC Shortcode
function wp_smart_toc_shortcode($atts, $content = null) {
    global $post;
    $content = $content ?: $post->post_content;
    // Match H2 and H3 headings
    preg_match_all('/<(h[2-3])[^>]*>(.*?)<\/\1>/i', $content, $matches, PREG_SET_ORDER);
    if (!$matches) return '';

    $toc = '<nav class="smart-toc"><ul>';
    $ids = [];
    foreach ($matches as $m) {
        $tag = strtolower($m[1]);
        $text = wp_strip_all_tags($m[2]);
        $id = sanitize_title($text);
        // Ensure unique IDs
        $id = isset($ids[$id]) ? $id . '-' . (++$ids[$id]) : ($ids[$id] = 1) && $id;
        $toc .= '<li class="toc-' . $tag . '"><a href="#' . esc_attr($id) . '">' . esc_html($text) . '</a></li>';
    }
    $toc .= '</ul></nav>';

    // Inject IDs into headings in content
    $new_content = preg_replace_callback(
        '/<(h[2-3])([^>]*)>(.*?)<\/\1>/i',
        function($m) use (&$ids) {
            $text = wp_strip_all_tags($m[3]);
            $id = sanitize_title($text);
            $id = isset($ids[$id]) ? $id . '-' . (++$ids[$id]) : ($ids[$id] = 1) && $id;
            return '<' . $m[1] . ' id="' . esc_attr($id) . '"' . $m[2] . '>' . $m[3] . '</' . $m[1] . '>';
        },
        $content
    );

    // Output TOC + modified content
    return $toc . $new_content;
}
add_shortcode('smart_toc', 'wp_smart_toc_shortcode');

// Enqueue scroll tracking JS and TOC styles
add_action('wp_footer', function() {
    ?>
    <style>
    .smart-toc { background: #f9f9f9; border:1px solid #eee; padding: 16px; margin-bottom: 24px; border-radius: 6px;}
    .smart-toc ul { list-style: none; margin: 0; padding: 0;}
    .smart-toc li { margin: 0 0 6px 0;}
    .smart-toc a { text-decoration: none; color: #0073aa;}
    .smart-toc a.active { font-weight: bold; color: #d54e21;}
    .toc-h3 { margin-left: 18px; font-size: 0.96em;}
    </style>
    <script>
    document.addEventListener('DOMContentLoaded', function() {
        const tocLinks = document.querySelectorAll('.smart-toc a');
        if (!tocLinks.length) return;
        const headings = Array.from(tocLinks).map(link => document.getElementById(link.getAttribute('href').substring(1)));
        window.addEventListener('scroll', function() {
            let activeIndex = 0;
            headings.forEach((h, i) => {
                if (h && h.getBoundingClientRect().top < window.innerHeight * 0.25) activeIndex = i;
            });
            tocLinks.forEach((link, i) => link.classList.toggle('active', i === activeIndex));
        });
    });
    </script>
    <?php
});
PHP

2. How to Use

In your post or page, simply wrap your content with [smart_toc]...[/smart_toc]:

[smart_toc]
<h2>Introduction</h2>
Content...
<h2>Features</h2>
Content...
<h3>Sub-feature</h3>
Content...
[/smart_toc]

Or, if you want to auto-TOC the whole post, just use [smart_toc] at the top of your post.

3. Features

  • Auto-generates TOC from H2/H3 headings
  • Highlights current section as you scroll
  • Lightweight (no jQuery or plugin dependency)
  • Works anywhere via shortcode
  • Customizable with simple CSS
Share this snippet!