Summary: Handy Client-side Table of Contents
Version: 2023-03-20
Prerequisites: PmWiki 2.2.56
Status: stable
Maintainer: Kathryn Andersen
Categories: TOC, Markup, PHP5, PHP72

Questions answered by this recipe

How can I create a table of contents for included files?
How can I create a table of contents for a page whose content is generated via pagelist?


Most of the existing table-of-contents recipes have this limitation: they can only generate a table of contents from the information in the source of the page they're used in. If data is included later (either with (:include :), or, even more tricky, with IncludeUpload) then they don't have the data to base the table-of-contents on.

This recipe solves that problem by generating the table-of-contents with JavaScript, which is run after the page has finished loading, so all the data is there.

Of course, the downside is that it requires JavaScript to be turned on; which many users and browsers may not. Caveat emptor.



To activate this recipe, unzip the archive; this will create a handytoc-(version) directory; you then need to move or copy the contents of this into your pmwiki directory. The directory contains:

File contains markup and functions.
Directory contains CSS and JavaScript files.

Add the following line to your local/config.php:



The simplest usage is to just put the (:htoc:) directive on the page where you want to have a table-of-contents.

You can add a title to your table of contents simply by adding it to the directive:

(:htoc This is my table of contents:)

There are also options.

startstart=2What level of header the table of contents starts at
endend=3What level of header the table of contents ends at
ignoreh1ignoreh1=trueThe ignoreh1= option can be either true or false. If true, then if there is only one H1 header in the content, then it will be ignored. If false, it won't be (providing that the start option equals 1).
classclass=htonelineset the class name for the div that the table of contents is inside. Useful for additional CSS styling for different kinds of ToCs.
anchorafterlementanchorafterelement=trueCan be true or false. If true, anchor is inserted after a heading (equivalent of !!! MyCoolHeading [[#tocLink9]]). If false, before (equivalent of !!! [[#tocLink9]] MyCoolHeading). This affects whether the heading is visible on the page after clicking on a link in the ToC.
smartanchorssmartanchors=trueCan be true or false. See below for details.
automaticanchorsautomaticanchors=trueCan be true or false. See below for details.
pathanchorspathanchors=trueCan be true or false. See below for details.


(:htoc start=2:)

The above will start with h2 headers.

(:htoc end=3:)

The above will end at h3 headers.

Note that if there are no H1 headers inside the content at all, then the table-of-contents will start at level 2 automatically (unless a greater start level is already specified), even of the headers are in an odd order.

Configuration variables

These can be set in local/config.php

SDV($HandyTocPubDir, "$FarmD/pub/handytoc"); SDV($HandyTocPubDirUrl, "\$FarmPubDirUrl/handytoc");

$HandyTocStartAtDefine the (default) level the table-of-contents starts at1
$HandyTocEndAtDefine the (default) level the table-of-contents ends at6
$HandyTocIgnoreSoleH1If true, then if there's only one h1 header in the content, it will be ignored.true
$HandyTocPubDirDirectory where the HandyToc css and js files are"$FarmD/pub/handytoc"
$HandyTocPubDirUrlUrl of directory where the HandyToc css and js files are"\$FarmPubDirUrl/handytoc"
$HandyTocAnchorAfterElementIf true, anchor is inserted after heading; otherwise, before.true
$HandyTocSmartAnchorsIf true, smart anchors are enabled (see below).false
$HandyTocAutomaticAnchorsIf true, automatic anchors are enabled (see below).false
$HandyTocPathAnchorsIf true, path anchors are enabled (see below).false
$HandyTocDefaultTitleProvides the default title, if none is provided in the page directive.empty (no title)
$HandyTocExcludeWithinSpecifies a CSS selector, all headings matching and within which will be excluded from the ToCempty (no exclusions)
$HandyTocVersionedAssetsIf true, the HandyTOC .js and .css file URLs will be versioned (i.e., suffixed with modification date)true

Smart anchors

The “smart anchors” option (off by default; enabled by setting $HandyTocSmartAnchors = true; in config.php or using the smartanchors=true argument in the (:htoc:) directive) uses a more modern and cleaner way of creating ToC anchor links. Instead of adding an <a> element next to a heading, it sets the id attribute of the heading element itself. If the heading already has an id attribute set, that existing id attribute is not overwritten, but kept intact, and used as the link (in place of the usual tocLink1, etc.).

(The reason for using this option is twofold: first, it avoids cluttering up the page source with unnecessary anchor tags; second, if your headings have semantically meaningful id values, your ToC will now use those values and therefore generate semantically meaningful links.)

Note: If smart anchors are enabled, the $HandyTocAnchorAfterElement setting is ignored (as it is inapplicable in that case).

Automatic anchors

The “automatic anchors” option (off by default; enabled by setting $HandyTocAutomaticAnchors = true; in config.php or using the automaticanchors=true argument in the (:htoc:) directive) causes anchor IDs (either the the id attribute of the <a> element next to a heading, or—if smart anchors are enabled—the id attribute of the heading element) to be generated from the text of the heading itself. The heading text is transformed for compatibility when it’s turned into an id: lowercase forms are used, spaces turned into dashes, periods into underscores, and all characters except alphanumerics, dashes, and underscores are removed. (For example, “My cool heading!” would get an id of my-cool-heading, while “Document section 5.2.1: odds & ends” gets an id of document-section-5_2_1-odds--ends.)

Just as with the “smart anchors” option, if the heading already has an id attribute set, that existing id attribute is not overwritten, but kept intact.

Path anchors NEW!

The “path anchors” option (off by default; enabled by setting $HandyTocPathAnchors = true; in config.php or using the pathanchors=true argument in the (:htoc:) directive) causes the automatically-generated anchor IDs (created by the automatic anchors option) to include all higher-level headings as well as the heading itself.

This can make anchor IDs more informative, and can often prevent duplicate IDs. For example, suppose that you have the following headings:

Chapter 1
  Section 1
  Section 2
Chapter 2
  Section 1
  Section 2

Without path anchors, the “Introduction” subheading under the “Chapter 1” heading would have the same ID (#introduction) as the “Introduction” subheading under the “Chapter 2” heading.

With path anchors, they would have IDs of #chapter-1---introduction and #chapter-2---introduction, respectively.

Note: This option has no effect if automatic anchors are not enabled.

Exclude within selector

The “exclude within” option allows you to specify a CSS selector (such as .some-class, #some-id, or #something-more-complex + p, .other-stuff:last-child). HandyTOC will then omit any headings that either match that selector, or are descendants of any element that matches that selector.

(This is useful for excluding heading elements within sidebars, floating frames, etc., from the table of contents.)

You can specify the exclude-within selector in config.php:

$HandyTocExcludeWithin = ".some-class, .other-class, #some-id";

Or, you can pass it as an option within the (:htoc:) directive:

(:htoc excludewithin=".some-class, .other-class, #some-id" :)

(If you do both, then the directive will override the config file.)

The exclude-within selector is empty by default (i.e., nothing is excluded).

CSS Styling

The table of contents is put inside a div with id='htoc', and the table-of-contents title (if there is one) is put in a H3 header. The <ul> element in the div has id='htoc_ul'. While the recipe comes with its own CSS styling (in pub/handytoc/handytoc.css) this can be overridden with your own custom CSS in pub/css/local.css

If the "class=" option is used, then the class is set for the same div; that is, it will be

    <div id="htoc" class="classname">

so keep that in mind when using this option.

There is also a .htoneline (handy-toc one line) class in the CSS file, which can be used (with the "class=" option) to make the table-of-contents all on one line, with | separators between each item. This is useful for pages where the headers are, say, single letters of the alphabet (as may happen with indexes). The CSS for this, however, may not work in less-advanced browsers.


This does not have a show/hide toggle button like some of the other TOC recipes; if it's defined for that page, then it's there all the time. If you want to show and hide the toc, you can use the Toggle recipe with markup such as the following:

(:toggle init=hide id=htoc lshow='Show Table of Contents' lhide='Hide Table of Contents':)

Note that this recipe will not work if the skin you are using does not have <!--HTMLFooter--> markup in it.

Release Notes

  • 2023-03-20: Added “path anchors” option.
  • 2021-11-10: Added “automatic anchors” and “versioned assets” options.
  • 2020-05-01: Fixed bug.
  • 2020-04-26: Added $HandyTocExcludeWithin.
  • 2017-12-17: Only inject HandyTableOfContents JS and CSS if a ToC is actually used.
  • 2017-11-26: Added “smart anchors” option.
  • 2017-11-05: Fixed bug that caused the same id to be assigned to multiple elements in the ToC block.
  • 2017-09-07: Fixed bug that would sometimes prevent the ToC from being created if $EnableHTMLCache was set.
  • 2017-08-22: Added id of htoc_ul to the <ul> element inside the #htoc div (to allow just the list to be shown/hidden by Toggle).
  • 2017-08-16: Added $HandyTocDefaultTitle.
  • 2017-08-16: Added $HandyTocAnchorAfterElement; fixed bug where "start"/$HandyTocStartAt would have no effect in many cases.
  • 2017-06-21: changed Markup for PHP 7.2 compatibility (HansB)
  • 2016-09-02: HansB: update for PHP5 compatibility.
  • (2007-02-18) Added "class=" option, and .htoneline CSS class
  • (2007-02-17) Put the JavaScript calls into $HTMLFooterFmt only if the markup is present.
  • (2007-02-11) Added $HandyTocIgnoreSoleH1 and ignore=h1
  • (2007-02-08) Now only looks inside #wikitext (page content); also fixed problems with different header levels in random order. Now XHTML compliant!
  • (2007-02-07a) fixed overlooked bug (forgot to rename something)
  • (2007-02-07) Initial version

See Also

PmWiki /
TableOfContents  Basic automatic table of contents and numbered headings
Cookbook /
Accordion  lightweight Accordion javascript requiring no framework (stable)
AutoTOC  Unobtrusive Automatic Table of Contents links (stable)
NumberedHeaders  Display numbered headers, indented paragraphs and table of contents (Stable)
NumberedSections  Add section numbers on a page (stable)
PageTableOfContents  Adds a clickable table of contents to a page (Stable)
QuickPageTableOfContents  Adds a dropdown clickable table of contents to a page - client side processing (Stable)
SlimTableOfContents  Simple or Numbered Table of Contents, Compatible with SectionEdit Recipe (not working with php5.5)
PmWiki /



See discussion at HandyTableOfContents-Talk

User notes +3: If you use, used or reviewed this recipe, you can add your name. These statistics appear in the Cookbook listings and will help newcomers browsing through the wiki.