Dynamic Headings in WordPress

One thing that’s always bugged me in writing Posts/Pages content within WordPress is that you have to cater for different presentation possibilities. If you’re into web-standards, then that makes life difficult for things like headings (h1, h2, etc), when a block of content is presented in different contexts.

Ideally, your page should be structured with an h1 tag around the title of the most important concept on the page, an h2 around a sub-topic/concept, etc. On your home page, the h1 usually ends up going around your logo/site title, since that’s the over-arching concept. Then under that, you might have a listing of recent posts. Each of those posts should probably have their titles in an h2. No problem so far, right? You just set up your template like that and you’re good to go.

In the content of each post however, this means that you’re down to an h3 if you have sub-sections within the post. No problem, just use that markup and everything’s golden. Until you view a single-post/page and then your structure doesn’t really make sense any more. Ideally, on these pages, the title of the post would be an h1 and those h3s would now be h2s. What to do?

Easy, drop the following code into your functions.php file (within your theme, or I guess you could turn it into a plugin if you were into that) and it will dynamically adjust your headings for your homepage (and archives, tag pages, etc).

function beaus_dynamic_headings( $content ) {
 if ( is_singular() )
 return $content;

 $content = str_replace( array( '<h5', '</h5>' ), array( '<h6', '</h6>' ), $content );
 $content = str_replace( array( '<h4', '</h4>' ), array( '<h5', '</h5>' ), $content );
 $content = str_replace( array( '<h3', '</h3>' ), array( '<h4', '</h4>' ), $content );
 $content = str_replace( array( '<h2', '</h2>' ), array( '<h3', '</h3>' ), $content );
 return $content;
add_filter( 'the_content', 'beaus_dynamic_headings' );

So when you’re writing a post, write to target a single-page view, where the title of the post/page itself is h1, and your structure within the post should start from there. The code above will take care of fixing it up for the homepage, archives etc (bumping all heading tags “down” one level).

  1. mdawaffe said:

    HTML5 allows for simpler markup with the same semantic meaning: http://www.w3.org/TR/html5/sections.html#rank (see also the outlining algorithm).

    Styling that markup can be tricky, though. The following is one possible approach.

    section h1 { // needs other sectioning elements as well: article aside nav
    font-size: smaller;

    To get rules matching the current default sizes is browser X, though, you need an Alot of css. That's a test stylesheet I made trying to replicate the default FF styling of headings for the markup HTML5 allows.

  2. demetris said:

    That is a clever solution to an annoying problem that I have avoided in my personal usage of WP only because I use teasers and excerpts on the front page and on index pages.

    Theme authors could consider that for inclusion by default in their themes.

    BTW, did you forget an operator in your IF statement or is my mind not working OK?

    • Beau Lebens said:

      This could definitely be a nice inclusion on some themes, depending on how they are implemented (as you say, in some cases this approach doesn't make sense).

      The way I've done the IF it is returning the content un-touched on singular Page or Posts, but applies the modifications everywhere else (so that I can catch archives, taxonomy pages, etc etc).

      • demetris said:

        Oh, right!

        I am not used to this implicit style (omitting braces and ELSEs), and it seems I was not reading very carefully, and I got really confused! ๐Ÿ˜€

        Thanks for the answer!

        • Beau Lebens said:

          It's a little (extra) confusing because of the lack of spacing/alignment. Eventually I'll get around to using a syntaxhighlighter plugin and update all my code snippets to being formatted correctly :-/

          I like this syntax though because it's concise, and just completely avoids code that is no longer relevant (in this case return-ing before looking at code that never needs to run). The thing with omitting braces when you only have a single line of code I go back and forth on, but I guess it saves a few keystrokes/lines ๐Ÿ˜‰

Comments are closed.