Automatic Table of Contents
Adds a linked table of contents above posts that have enough headings, and gives each heading an anchor so readers can jump straight to a section.
Opens WPIDE on
Code
// Minimum number of headings before the table of contents appears.
$wpide_toc_min_headings = 3;
// Heading levels to include (2 = h2, 3 = h3 ...).
$wpide_toc_levels = array( 2, 3 );
// Title shown above the list.
$wpide_toc_title = 'Contents';
add_filter( 'the_content', function ( $content ) use ( $wpide_toc_min_headings, $wpide_toc_levels, $wpide_toc_title ) {
if ( ! is_singular( 'post' ) || ! in_the_loop() || ! is_main_query() ) {
return $content;
}
$levels = implode( '', array_map( 'absint', $wpide_toc_levels ) );
$pattern = '/<h([' . $levels . '])([^>]*)>(.*?)<\/h\1>/is';
if ( ! preg_match_all( $pattern, $content, $found ) || count( $found[0] ) < $wpide_toc_min_headings ) {
return $content;
}
$used = array();
$items = array();
$result = preg_replace_callback( $pattern, function ( $m ) use ( &$used, &$items ) {
$text = trim( wp_strip_all_tags( $m[3] ) );
$attrs = $m[2];
if ( preg_match( '/\sid=["\']([^"\']+)["\']/i', $attrs, $id_match ) ) {
$id = $id_match[1];
} else {
$base = sanitize_title( $text );
$base = '' !== $base ? $base : 'section';
$id = $base;
$i = 2;
while ( isset( $used[ $id ] ) ) {
$id = $base . '-' . $i++;
}
$attrs .= ' id="' . esc_attr( $id ) . '"';
}
$used[ $id ] = true;
$items[] = array( 'level' => (int) $m[1], 'id' => $id, 'text' => $text );
return '<h' . $m[1] . $attrs . '>' . $m[3] . '</h' . $m[1] . '>';
}, $content );
if ( null === $result || ! $items ) {
return $content;
}
$top = min( wp_list_pluck( $items, 'level' ) );
$html = '<nav class="wpide-toc" aria-label="' . esc_attr( $wpide_toc_title ) . '" style="margin:0 0 2em;padding:1em 1.25em;border:1px solid rgba(0,0,0,.12);border-radius:6px;">';
$html .= '<p style="margin:0 0 .5em;"><strong>' . esc_html( $wpide_toc_title ) . '</strong></p><ul style="margin:0;">';
foreach ( $items as $item ) {
$indent = $item['level'] > $top ? ' style="margin-left:' . ( ( $item['level'] - $top ) * 1.25 ) . 'em;"' : '';
$html .= '<li' . $indent . '><a href="#' . esc_attr( $item['id'] ) . '">' . esc_html( $item['text'] ) . '</a></li>';
}
return $html . '</ul></nav>' . $result;
}, 20 );
Notes
Change the minimum heading count, the heading levels or the title in the variables at the top.
How to add this snippet
- Install WPIDE on your WordPress site (it is free) and open Code Snippets.
- Use snippet above adds it from the library in one click. Or copy the code into a new snippet with the same code type.
- It is added inactive: review the code and settings, then switch it on.