Grow your CSS skills. Land your dream job.

Automatic Table of Contents

Published by Chris Coyier

Any long page of content with distinct and well marked up content can benefit from a table to contents. A table of contents provides a quick way to jump down the page to the desired section. Of course you can create a table of contents manually, but it may be smart to build it dynamically on-the-fly with JavaScript. This is true for several reasons:

  • It's easier - write the JavaScript once and it can create the Table on Contents on every page you need it.
  • It's more reliable - the JavaScript isn't subject to authoring errors.
  • It's still accessible - A table of contents is good for the general concept of accessibility, but it is a bonus (not having it doesn't ruin the page) and nearly all screen readers run JavaScript.

This kind of thing has been done many times and many ways. But this time is ours! Plus it makes for a good tutorial.


A live example of this can be found on CodePen's PRO feature pages like this one for Professor Mode.

HTML: Headers and IDs

A long page of different parts you wish to link to could be marked up a bunch of ways. Perhaps a FAQ page could be a <dl>. It could literally be <section> after <section>. In our case, we'll assume this structure:

<article>

   <h3 id="question-one">Title of Question</h3>
   <!-- whatever other content, probably some paragraphs and stuff. -->

   <h3 id="question-two">Another Question</h3>
   <!-- whatever other content, probably some paragraphs and stuff. -->

   <!-- etc -->

</article>

A perfectly legit page full of headers with IDs and the content between them. Note the ID's. They are unique, as any good ID ought to be. This is required because it gives us a link target.

A link like this:

<a href="#question-one">Link to Question One</a>

Will jump down the page when clicked until the element with the ID "question-one" is in view.

Building the Table of Contents with jQuery

Our goal is to inject HTML on the page in the form of a table of contents. Like this:

<nav role="navigation" class="table-of-contents">
  <h2>On this page:</h2>
  <ul>
    <li><a href="#question-one">Question One</a></li>
  </ul>
</nav>

A list in that <nav>? Yep.

Step 1: A string of HTML

We'll build this entirely dynamically. Perhaps it would be smart to use some kind of JavaScript templating for this. But hey, this is so simple, let's just build a big string and append that.

var ToC =
  "<nav role='navigation' class='table-of-contents'>" +
    "<h2>On this page:</h2>" +
    "<ul>";

We're leaving several tags open there, we'll close them before we are done.

Step 2: Loop through the headers

The <h3>'s on our page indicate each section we wish to link to, so we'll find them all with a jQuery selector, then loop through each of them.

$("article h3").each(function() {

  // loop

});

Step 3: Get the bits of data we need

We need 1) the text of each header, which we will turn into a link and 2) the ID of each header which we can turn into a href attribute for that link.

var el, title, link;

$("article h3").each(function() {

  el = $(this);
  title = el.text();
  link = "#" + el.attr("id");

});

Inside of that loop, "this" refers to the header element currently targeted, so to speak. we set "el" to a jQuery version of it, so we can use jQuery methods on it to extract that text and ID.

Step 4: Create a new list item and append to string

var newLine, el, title, link;

$("article h3").each(function() {

  el = $(this);
  title = el.text();
  link = "#" + el.attr("id");

  newLine =
    "<li>" +
      "<a href='" + link + "'>" +
        title +
      "</a>" +
    "</li>";

  ToC += newLine;

});

The "+=" there means "append this new string to the already existing string stored in this variable.

Step 5: Close the "template"

ToC +=
   "</ul>" +
  "</nav>";

Step 6: Inject HTML onto page

Now you'll need to decide just exactly where you want this newly formed table to contents to be injected onto the page. Putting at the top of the page is probably smart.

Our example uses <article> to wrap everything, so to inject at the top of that, we would do:

$("article").prepend(ToC);

In "real life", perhaps you'd target a header and use insertAfter or another of jQuery's fancy DOM insertion methods.

Demo

Table of Contents demo on CodePen

For a bit of extra flair in the demo, I used the :target selector to highlight the title of the question when you jump down the page to it. Just gets the eye there even quicker. Like from this tutorial.

Comments

  1. Myles Cowper-Coles
    Permalink to comment#

    Ah, thats great. Nice and simple solution, just what I was looking for. Lucky I was on twitter when you tweeted it.

    • P.S Scroll down :D

    • Colin Wiseman
      Permalink to comment#

      Wow! Shahmir! You have blog on Linux AND it looks good! :D (i.e. that’s my way of saying nice blog matey!)

    • Thanks @Colin

    • Hanèn
      Permalink to comment#

      Great Job!

    • Kevin
      Permalink to comment#

      You’re absolutely full of it. Mon has nothing to do with automated ToC or this article in any way whatsoever. Nice blackhat tactics.

    • @Kevin, Im not sure what you are talking about. The link has a JavaScript generated Table of Content generated on the left, when you scroll down the article.

    • Kevin
      Permalink to comment#

      My apologies. I thought you meant in your post that you were referencing a tutorial that you’d already written on this subject rather than an example of a working ToC. Very nice blog by the way.

      Cheers,

      Kevin

    • Very Nice Shahmir.

      Yours is better in my opinion. It utilizes all headings, not just h3 like in the example here. Also yours indents the different levels of headings.

  2. Michael Martin
    Permalink to comment#

    Hey! What about HTML5 headings, sections, and outlines? Be great to have your take.

  3. Glynn
    Permalink to comment#

    I agree full with the idea of TOC’s and how how useful they can be on large as can site maps for large websites. The only thing I feel here is slightly mis-leading is that there is no mention of other options to JS which I would personally consider before JS.

    I know that like 99.9% of the internet is viewed with js enabled but fallbacks aren’t hard or in this case considering your other options first. Many of us are not working on static pages anymore and probably have access / using languages which could do this server side for us.

    The TOC can in some ways be even more of a benefit to less able users who could be using a screen reader or only a keyboard to navigate. So we dont want to limit these people to require js to a tool which will enhance their experience and making reading easier.

    • Glynn
      Permalink to comment#

      I should proof read stuff more

      I agree fully with the idea of TOC’s and how how useful they can be on large pages as can site maps for large websites

    • @Glynn – Agreed. I’d rather do it server-side. Of course for a developer, if you are a developer it’s not very hard to do.

  4. Ian
    Permalink to comment#

    Cool idea. I was just thinking yesterday with everything we can do with html5 and js who needs MS Word to make a document. This just adds to the list. And no one would have to have MS Word or OpenOffice installed to read it.

  5. Maciej Baron
    Permalink to comment#

    You shouldn’t be generating your HTML by using strings, that’s bad practice.

    Instead, try using:

    var ToC = $("<nav>").attr("role", "navigation").addClass("table-of-content");
    (...)
    
    newline = $("<li>").text("title");
    
    (...)
    ToC.append(newline);
    
    • Permalink to comment#

      What makes it bad practice? Your’s calls jQuery twice more than Chris’ where his uses strait up JavaScript. Also, your’s is now jQuery dependant where Chis’s HTML generating is not.

      Also, I think there is a mistake in the example code. The anchors (opening and closing) are not showing up.

    • Maciej Baron
      Permalink to comment#

      Because it’s less “safe” – otherwise you have to remember to close tags, you can make mistakes in your HTML etc..

    • Maciej Baron
      Permalink to comment#

      Oh, and btw,:

      “Chris’ where his uses strait up JavaScript (…) your’s is now jQuery dependant where Chis’s HTML generating is not”

      I don’t think you had a look at the sourcecode but he clearly uses jQuery.

    • Marcel
      Permalink to comment#

      One might wonder whether using traditional JavaScript isn’t a bit better suited for such a task, but if you’re working with a library you might as well stick with it.

      Though I think your code should be more like:

      (...)
      
      newline = $("<li>").append( $("<a>").attr('href', link).text(title) );
      
      (...)
    • Permalink to comment#

      I was referring to just the HTML generating portion. As well as this… http://browserdiet.com/#dont-use-jquery

    • Maciej Baron
      Permalink to comment#

      Josh, I’ve given an example in jQuery (because it provides handy shortcuts) but you can do the same thing using JavaScript (with a bit more code). He’s not doing the same thing in vanilla JavaScript. The link you’ve posted is true for the example entailed on that page, in this case however it is completely irrelevant. Bottom line is you shouldn’t write your HTML using concatenated strings, period. There are many reasons why it’s a lot better doing it the way I’ve suggested: everything gets nicely escaped, your HTML will be without structural errors, you have to worry about fewer things. I hope Chris will acknowledge that.

    • All fair points. The worst thing you could do, though, is to put the .append() within the loop. So you are doing DOM manipulation every single iteration. My thinking with the string was that at least you only hit the DOM once.

    • Maciej Baron
      Permalink to comment#

      But you’re working on a document fragment that only gets appended to the actual page after the loop has finished. So actually the page gets rerendered only once.

    • Ian
      Permalink to comment#

      I like to carry it a step further and pass in a object to leave out the method chaining.

      
          var toc = $('<nav>', {
              'role': 'navigation',
              'class': 'table-of-contents'
          });
      

      Don’t know if there are any drawbacks to that.

  6. Zachary Kain and I use a bit of looping magic in Sass for the TOC of http://typeplate.com

    Here’s the link to our source
    #L347-L380

    You can look through the code to get an overview, but the loop looks like this…

    // epic loop wizardy!
    @for $i from 1 through 4 {
        .toc ol:nth-of-type(#{$i}) li:before {
        content: "#{$i}." counter(item) " – ";
        }
    }
    

    We’re also using the counter-reset CSS property. The result from all this magic is the following…

    1.1 Item
    1.2 Item
    2.1 Item
    2.2 Item
    
    • I feel like I’m missing something. Why are you using Sass to do this when you could use simple CSS counters? On the other hand, you could use Sass without using CSS counter. It’s the mix of both which makes no sense to me.

  7. Permalink to comment#

    That’s awesome! And I love the :target formatting you added

  8. Very cool. Looks like you missed the anchor link in step 4, should be sumpin’ like:

    newLine = "<li><a href='" + link + "'>" + title + "</li></a>";

  9. kelly johnson
    Permalink to comment#

    Good stuff! I remember finding the :target technique in the Smashing CSS book by Eric Meyer and when I did, that was the first thing I did when applicable!

  10. As long as you’re there, why not use Javascript to further alter the HTML adding the anchor links to the H3?

  11. Permalink to comment#

    Combining this with Waypoints would be epic… I am thinking functionality similar to this: http://webdesigntutsplus.s3.amazonaws.com/tuts/313_waypoints/demo/index.html

  12. Great way of creating a table of content. It is specially useful in Blogs. I will use it in my new redesign. Thanks for sharing Chris!

  13. I’m not entirely certain of this, but isn’t each an asynchronous function? Therefore, you have no real guarantee that all the items have been added before you prepend them.

  14. We did this for a client ages ago when it just wasn’t practical to include it as part of the back end build.

    A couple of other features we included to append a “Back to top” link above each of the h3’s except for the first one. Daves suggestion for adding an incremental ID to the H3’s was also something that we included as well.

    Finally we ran a check to see if there was more than 2 headings before creating the table of contents, otherwise it seems just a little light on.

  15. Pete
    Permalink to comment#

    I usually do this in php. Why tax the browser?

    • If you have structured data, sure, that would be smart. Like if each FAQ entry was it’s own database entry. In my case, and I suspect quite a few others, the FAQ is just content stored in one block. With PHP, you’d have to read that content and parse it somehow? output buffering? regexes? I don’t even know. Doesn’t seem as easy to me. Since this is non-essential content, it doesn’t bother me to do it in the browser. But if you wanted to write up a tutorial on doing it with PHP, that would be interesting to read!

    • Marcel
      Permalink to comment#

      Chris, I think you are trying to turn PHP into JavaScript and I think this approach will cause more harm than good. The crucial difference that I think ought to be noticed is that JavaScript is meant to alter the DOM, while PHP is meant to create it (this is my loose explanatation, but I think it’s correct).

      The only viable PHP solution that I think is worth considering is to create a simple FAQ (or similar) management system. All you need, is to save two pieces of data (per each post!) to the database.

      See this fiddle: http://phpfiddle.org/main/code/4s3-uw6.

      If you want to traverse the DOM and alter it after it has been generated, than PHP is not the solution – JavaScript is.

    • I’m not disagreeing that doing this server side is a good idea if you have the data in appropriate structured way in the database.

      If you have a huge amount of pages on a website in this FAQ format, it would make sense to customize a CMS system do to it this way. Essentially a custom field pair (title/answer) that could be repeated multiple arbitrary times.

      If you have a handful of FAQ pages, as I had, a twenty-some line JavaScript solution that works on any set of text works great.

    • Marcel
      Permalink to comment#

      I see what you are saying, however you will very likely sacrifice functionality for [your] comfort.

      If and when a user with disabled JavaScript browses your page, he/she will not be able to access your navigation, which is somewhat crucial. This becomes particularly challenging when your visitor is disabled (blindness is what I really mean).

      Do you think it would be viable to do this manually as opposed to having JavaScript create your ToC structure?

    • If it’s crucial, it’s crucial. If it’s not, it’s not.

      I specifically thought about the accessibility of this and talked with people about it. Most significantly:

      • 98% of screen readers support JavaScript
      • Even if they didn’t, lack of this ToC doesn’t hurt the accessibility. They can still tab through headers and access all the content.

      And yep, you can do it manually too. I bet we could think of a couple more ways too. This is a tutorial about one way. Forgive me if I sound defensive, I just really dislike dogmatism in these things.

    • Marcel
      Permalink to comment#

      Yeah, I think you are right. Though as a web developer, this is what annoys me the most – the tiny things that appear to be the simplest usually end up being the most problematic.

      It’s ‘easy’ to create a whole system to perform a task, but efficiently solving something like this is easier said than done.

      Either way, I think this is a great article and gives us lots to think about as far as JS dependance.

  16. The only issue with this method is from an SEO perspective, that the TOC may not be crawled by search engines. I use the word ‘may’ because search engines are becoming better and cleverer at picking up any links generated via JavaScript, however, this cannot be relied upon 100%. So generating the TOC server-side (e.g. Node.js, PHP, Ruby) in a similar automated fashion would be the best recommendation.

    • Ian Lusk
      Permalink to comment#

      Remember though, all of the h3 tags in this example are included in the raw body of the HTML. If a search engine hit the page and disregarded JS implemented elements, it’d still be picking up on the semantic structure of the page itself, so you wouldn’t lose anything. All the keywords, questions, and answers would still be on the page by default.

    • Jim S. Smith
      Permalink to comment#

      I agree with Ian on this one.

      The search-engines are more concerned with the actual page content, the “title”, and certain “meta”-tags (less so on the “keywords”) in the head section. I doubt the absence of JavaScript abilities would keep the rest of the page’s content from being visible to search-engines.

      Also, very rarely is anything (solutions or otherwise) going to be absolutely “100% reliable”. That is not a realistic expectation anyway.

      I might look up the idea of using PHP server-side to render those sections IF it was detected that the browser has JavaScript “disabled”. However, that is up to the website’s owner/designer.

  17. Great tutorial. Great discussion. Any chance of someone who knows that they are doing (not me) produce the same script with auto-generated anchors for headings?

    “If you wanted to make it even less subject to an author messing up and forgetting to ID their H3′s, you could assign ID’s using an incremental value (count) in the each function. Squeeze in something like below $(this).attr(“id”, “question-” + count);”

    • Jim S. Smith
      Permalink to comment#

      Actually, Pat,

      I believe that was already given in an above post, but it still is a good idea.

      It would not be very hard to count all the section headers and then assign an “id”-attribute (plus its sequential number) using an increment loop. I have seen a bit of PHP (though, not what is being discussed here) doing something similar using arrays in form fields.

    • Marcel
      Permalink to comment#

      Pat, I have whipped up Chris’ post into a basic jQuery plugin, with an extra set of options.

      Check out this fiddle: http://jsfiddle.net/AwRHz/.

      Let me know what you think.

    • Marcel, Thanks a lot!. Lovely piece of code you “whipped up.”

    • Marcel
      Permalink to comment#

      Forgot to add a bit of code there, use this link instead: http://jsfiddle.net/AwRHz/5/.

    • Permalink to comment#

      It’s possible to add unique ids to the id-less h3s:

      $('article h3:not([id])').each(
          function() { 
              this.id = 'q' + Math.random().toString(36).substr(2);
          }
      );
      
  18. Jim S. Smith
    Permalink to comment#

    This is a wonderful idea!

    Being that I have been playing around with a new blog I installed, this would make a fine addition to some of my more-lengthy posts. I was thinking of using a “Table-Of-Contents” in some of them, but now – I see that is very simple to automate that process.

    I may have to see how well it works inside of “div”-tags too!

  19. Earlier I made a comment about combining your idea with jQuery Waypoints, so I went ahead and did it and threw the code up on CodePen. I also added flexMenu to make it a fully responsive, sticky, automatic table of contents. I thought I’d share. Thanks for the constantly excellent blog.

    Auto, sticky, responsive ToC on CodePen: http://cdpn.io/mqigo

    Credits:
    This blog post (of course).

    http://webdesign.tutsplus.com/tutorials/javascript-tutorials/create-a-sticky-navigation-header-using-jquery-waypoints/

    http://webdesign.tutsplus.com/tutorials/site-elements/a-flexible-approach-to-responsive-navigation/

    • Woah! For some reason that CodePen link isn’t loading external scripts. Use this link instead:

    • LeahGrrl
      Permalink to comment#

      This looks wonderful and it works so nicely on CodePen. Now if I just knew what to do with all your hard work, I’d love to make it work on my eportfolio.

  20. Brad Dalton
    Permalink to comment#

    You could also display the TOC in the sidebar widget which is a good idea if you have images before you content.

  21. Many great implementations here but the winner for me is the last one by Joel Newcomer. Very well done and I love that is responsive and sticky. Very useful indeed.

  22. Useful technique…i like it…gonna try it as well.

  23. This is awesome. Chris, your write-ups are always incredibly helpful and insightful. This site rocks!

    My only concern with this TOC approach is that I think a server-side solution would be better for SEO purposes, as Jasdeep (above) also mentioned. Specifically, I think you might not get rich “jump to” snippets in search results (eg: http://yoast.com/jump-to-snippets-optimization/) if the TOC is generated via JS.

    But I would love to hear some other opinions on this. Perhaps google is smart enough and willing to give rich snippets for JS generated content? Or maybe google doesn’t need the TOC to do it?

    • I just don’t think SEO is a factor. You aren’t adding content to the page, just a few links that link to content that already exists.

    • Jim S. Smith
      Permalink to comment#

      I think what some are missing here, as far as concerns about the “SEO-ability” of a website using this method of TOC-generation, is the fact that the search-engine bots are going to have the webpage(s) rendered to them in the same fashion that they are rendered to the users’ browsers.

      The webpages will still be generated in much the same way, regardless – to include all generated headers, lists, etc. This is the case regardless if the page elements were generated “on-the-fly” with client-side scripting or pre-rendered, server-side (PHP, etc).

      If you were to generate a large number of TOC links due to a fairly large sized, formatted document, this just makes even more sense to use such a looped function. This could also result in using less storage space for the original document.

      I think some of the concerns expressed here may be a little “over-the-top” and overly-cautious. With some of the newer tags introduced in HTML 5, this bit of scripting may find an even bigger reason for its usefulness.

  24. Permalink to comment#

    I don’t like this kind of thing being implemented with JavaScript. Even though a TOC isn’t extremelly important, it’s still useful enough to be missed, therefore a JS TOC isn’t unobstructive IMHO.

    Years ago I developed a TOC builder for WordPress. Anywhere in post content I can use shortcode to add TOC itens, with hierarchy support, and another shortcode adds the TOC where I want it. Unfortunatelly I didn’t finish it before getting a full time job and never worked on it again :(

  25. Crispen Smith

    Excellent article, as always. I am just a little confused about your decision to use the jquery version of the el derived from the H3. Neither I’d nor innerHTML are that hard to fetch from a native JavaScript object. My understanding is that current wisdom is not to jquerify an element unless strictly needed.

  26. Permalink to comment#

    This is awesome! Great post. I love the :target selector you are using. Perfecto

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".