Fork me on GitHub

Table of Contents plugin for Bootstrap

Build Status

This Bootstrap plugin allows you to generate a table of contents for any page, based on the heading elements (<h1>, <h2>, etc.). It is meant to emulate the sidebar you see on the Bootstrap v3 documentation site.

This page is an example of the plugin in action – the table of contents you see on the left (or top, on mobile) was automatically generated, without having to manually keep all of the navigation items in sync with the headings.

IDs are created on the heading elements if they aren’t already present. Unicode characters are supported. While IDs are added for developer convenience, you’ll want to do this on the backend / through your static site generator so that your users can link to the anchors via URL fragments.

Usage

  1. Set up jQuery
  2. Set up Bootstrap v4 or Bootstrap v5
  3. Include the Bootstrap Table of Contents stylesheet and JavaScript file. Unminified versions are also available.

    <!-- add after bootstrap.min.css -->
    <link
      rel="stylesheet"
      href="https://cdn.rawgit.com/afeld/bootstrap-toc/v1.0.1/dist/bootstrap-toc.min.css"
    />
    <!-- add after bootstrap.min.js or bootstrap.bundle.min.js -->
    <script src="https://cdn.rawgit.com/afeld/bootstrap-toc/v1.0.1/dist/bootstrap-toc.min.js"></script>
    
  4. Pick one of the two options below.
  5. Determine the layout.

Via data attributes

Simplest.

Create a <nav> element with a data-toggle="toc" attribute.

<nav id="toc" data-toggle="toc"></nav>

You can put this wherever on the page you like. Since this plugin leverages Bootstrap’s Scrollspy plugin, you will also need to add a couple attributes to the <body>:

Bootstrap v4 Scrollspy

<body data-spy="scroll" data-target="#toc"></body>

Bootstrap v5 Scrollspy

<body data-bs-spy="scroll" data-bs-target="#toc"></body>

Via JavaScript

If you need customization.

If you prefer to create your navigation element another way (e.g. within single-page apps), you can pass a jQuery object into Toc.init().

<nav id="toc"></nav>
$(function () {
  var navSelector = "#toc";
  var $myNav = $(navSelector);
  Toc.init($myNav);
  $("body").scrollspy({
    target: navSelector,
  });
});

See the Scrollspy documentation for more information about initializing that plugin.

Options

When calling Toc.init(), you can either pass in the jQuery object for the <nav> element (as seen above), or an options object:

Toc.init({
  $nav: $("#myNav"),
  // ...
});

All options are optional, unless otherwise indicated.

option type notes
$nav jQuery Object (required) The element that the navigation will be created in.
$scope jQuery Object The element where the search for headings will be limited to, or the list of headings that will be used in the navigation. Defaults to $(document.body).

Customization

Headings

By default, the plugin chooses the top-level navigation items by searching for headings at the first heading level, then works its way down (<h1>, then <h2>, etc.) It will stop when it finds the first set of headings where more than one exists at that level. For example:

<h1>The title</h1>
<h2>Some sub-title</h2>
...
<h3>Section 1</h3>
<h4>Subsection A</h4>
...
<h4>Subsection B</h4>
...
<h3>Section 2</h3>

The plugin would see there’s only one <h1>, then that there’s only one <h2>, then stop when it sees there’s more than one <h3>. The identified level becomes the top-level navigation items in the Table of Contents, and any headings under those (the <h4>s in this case) would be the second-level navigation.

This behavior can be customized with the $scope option. That jQuery object can be created with one or more selectors to force certain headings to be used.

In the above example, let’s say that you wanted the navigation to only contain the Subsections. You could pass:

Toc.init({
  $scope: $("h4"),
  // ...
});

and the resulting Table of Contents would only contain:

  • Subsection A
  • Subsection B

Displayed text

By default, Bootstrap TOC will use the text from the heading element in the table of contents. If you want to customize what is displayed, add a data-toc-text attribute to the heading with the desired text. For example:

<h2 data-toc-text="Short text">Longer text</h2>

displays “Longer text” as the heading, but “Short text” in the sidebar.

Skipping

To prevent a particular heading from being added to the table of contents, add a data-toc-skip boolean attribute to the heading.

<h2 data-toc-skip>Some heading you don't want in the nav</h2>

Layout

This plugin isn’t opinionated about where it should be placed on the page, but a common use case is to have the table of contents created as a “sticky” sidebar.

<body data-spy="scroll" data-target="#toc">
  <div class="container">
    <div class="row">
      <!-- sidebar, which will move to the top on a small screen -->
      <div class="col-sm-3">
        <nav id="toc" data-toggle="toc" class="sticky-top"></nav>
      </div>
      <!-- main content area -->
      <div class="col-sm-9">...</div>
    </div>
  </div>
</body>

You may also want to include this in your stylesheet:

nav[data-toggle="toc"] {
  top: 42px;
}

/* small screens */
@media (max-width: 768px) {
  /* override stickyness so that the navigation does not follow scrolling */
  nav[data-toggle="toc"] {
    margin-bottom: 42px;
    position: static;
  }

  /* PICK ONE */
  /* don't expand nested items, which pushes down the rest of the page when navigating */
  nav[data-toggle="toc"] .nav .active .nav {
    display: none;
  }
  /* alternatively, if you *do* want the second-level navigation to be shown (as seen on this page on mobile), use this */
  /*
  nav[data-toggle='toc'] .nav .nav {
    display: block;
  }
  */
}

Note: if you’re upgrading from version <= 0.4.1 to 1.0.0+, these have changed.

Examples

See also

This plugin was heavily inspired by: