Earlier this 12 months, I self-published an book known as Understanding JavaScript Guarantees (free for obtain). Despite the fact that I didn’t have any intention of turning it right into a print e-book, sufficient individuals reached out inquiring a couple of print model that I made a decision to self-publish that as properly .I believed it might be a simple train utilizing HTML and CSS to generate a PDF after which ship it off to the printer. What I didn’t notice was that I didn’t have a solution to an necessary a part of a print e-book: the desk of contents.
The make-up of a desk of contents
At its core, a desk of contents is pretty easy. Every line represents part of a e-book or webpage and signifies the place yow will discover that content material. Sometimes, the traces comprise three elements:
- The title of the chapter or part
- Leaders (i.e. these dots, dashes, or traces) that visually join the title to the web page quantity
- The web page quantity
A desk of contents is straightforward to generate inside phrase processing instruments like Microsoft Phrase or Google Docs, however as a result of my content material was in Markdown after which remodeled into HTML, that wasn’t a great possibility for me. I needed one thing automated that will work with HTML to generate the desk of contents in a format that was appropriate for print. I additionally needed every line to be a hyperlink so it might be utilized in webpages and PDFs to navigate across the doc. I additionally needed dot leaders between the title and web page quantity.
And so I started researching.
I got here throughout two wonderful weblog posts on making a desk of contents with HTML and CSS. The primary was “Construct a Desk of Contents out of your HTML” by Julie Blanc. Julie labored on PagedJS, a polyfill for lacking paged media options in net browsers that correctly codecs paperwork for print. I began with Julie’s instance, however discovered that it didn’t fairly work for me. Subsequent, I discovered Christoph Grabo’s “Responsive TOC chief traces with CSS” publish, which launched the idea of utilizing CSS Grid (versus Julie’s float-based strategy) to make alignment simpler. As soon as once more, although, his strategy wasn’t fairly proper for my functions.
After studying these two posts, although, I felt I had a ok understanding of the structure points to embark alone. I used items from each weblog posts in addition to including some new HTML and CSS ideas into the strategy to provide you with a consequence I’m pleased with.
Selecting the proper markup
When deciding on the proper markup for a desk of contents, I believed primarily concerning the right semantics. Basically, a desk of contents is a couple of title (chapter or subsection) being tied to a web page quantity, virtually like a key-value pair. That led me to 2 choices:
- One possibility is to make use of a desk (
<desk>
) with one column for the title and one column for the web page. - Then there’s the usually unused and forgotten definition listing (
<dl>
) aspect. It additionally acts as a key-value map. So, as soon as once more, the connection between the title and the web page quantity can be apparent.
Both of those appeared like good choices till I spotted that they actually solely work for single-level tables of contents, specifically, provided that I needed to have a desk of contents with simply chapter names. If I needed to point out subsections within the desk of contents, although, I didn’t have any good choices. Desk parts aren’t nice for hierarchical information, and whereas definition lists can technically be nested, the semantics didn’t appear right. So, I went again to the drafting board.
I made a decision to construct off of Julie’s strategy and use a listing; nonetheless, I opted for an ordered listing (<ol>
) as an alternative of an unordered listing (<ul>
). I feel an ordered listing is extra acceptable on this case. A desk of contents represents a listing of chapters and subheadings within the order through which they seem within the content material. The order issues and shouldn’t get misplaced within the markup.
Sadly, utilizing an ordered listing means dropping the semantic relationship between the title and the web page quantity, so my subsequent step was to re-establish that relationship inside every listing merchandise. The simplest technique to clear up that is to easily insert the phrase “web page” earlier than the web page quantity. That means, the connection of the quantity relative to the textual content is obvious, even with out some other visible distinction.
Right here’s a easy HTML skeleton that shaped the idea of my markup:
<ol class="toc-list">
<li>
<a href="#link_to_heading">
<span class="title">Chapter or subsection title</span>
<span class="web page">Web page 1</span>
</a>
<ol>
<!-- subsection gadgets -->
</ol>
</li>
</ol>
Making use of kinds to the desk of contents
As soon as I had established the markup I deliberate to make use of, the following step was to use some kinds.
First, I eliminated the autogenerated numbers. You may select to maintain the autogenerated numbers in your personal undertaking when you’d like, nevertheless it’s widespread for books to have unnumbered forewords and afterwords included within the listing of chapters, which makes the autogenerated numbers incorrect.
For my function, I might fill within the chapter numbers manually then alter the structure so the top-level listing doesn’t have any padding (thus aligning it with paragraphs) and every embedded listing is indented by two areas. I selected to make use of a 2ch
padding worth as a result of I nonetheless wasn’t fairly positive which font I might use. The ch
size unit permits the padding to be relative to the width of a personality — it doesn’t matter what font is used — somewhat than an absolute pixel dimension that would wind up wanting inconsistent.
Right here’s the CSS I ended up with:
.toc-list, .toc-list ol {
list-style-type: none;
}
.toc-list {
padding: 0;
}
.toc-list ol {
padding-inline-start: 2ch;
}
Sara Soueidan identified to me that WebKit browsers take away listing semantics when list-style-type
is none
, so I wanted so as to add function="listing"
into the HTML to protect it:
<ol class="toc-list" function="listing">
<li>
<a href="#link_to_heading">
<span class="title">Chapter or subsection title</span>
<span class="web page">Web page 1</span>
</a>
<ol function="listing">
<!-- subsection gadgets -->
</ol>
</li>
</ol>
Styling the title and web page quantity
With the listing styled to my liking, it was time to maneuver on to styling a person listing merchandise. For every merchandise within the desk of contents, the title and web page quantity should be on the identical line, with the title to the left and the web page quantity aligned to the appropriate.
You is likely to be pondering, “No drawback, that’s what flexbox is for!” You aren’t fallacious! Flexbox can certainly obtain the proper title-page alignment. However there are some tough alignment points when the leaders are added, so I as an alternative opted to go together with Christoph’s strategy utilizing a grid, which as a bonus because it additionally helps with multiline titles. Right here is the CSS for a person merchandise:
.toc-list li > a {
text-decoration: none;
show: grid;
grid-template-columns: auto max-content;
align-items: finish;
}
.toc-list li > a > .web page {
text-align: proper;
}
The grid has two columns, the primary of which is auto
-sized to refill all the width of the container, minus the second column, which is sized to max-content
. The web page quantity is aligned to the appropriate, as is conventional in a desk of contents.
The one different change I made at this level was to cover the “Web page” textual content. That is useful for display screen readers however pointless visually, so I used a conventional visually-hidden
class to cover it from view:
.visually-hidden {
clip: rect(0 0 0 0);
clip-path: inset(100%);
top: 1px;
overflow: hidden;
place: absolute;
width: 1px;
white-space: nowrap;
}
And, in fact, the HTML must be up to date to make use of that class:
<ol class="toc-list" function="listing">
<li>
<a href="#link_to_heading">
<span class="title">Chapter or subsection title</span>
<span class="web page"><span class="visually-hidden">Web page</span> 1</span>
</a>
<ol function="listing">
<!-- subsection gadgets -->
</ol>
</li>
</ol>
With this basis in place, I moved on to handle the leaders between the title and the web page.
Creating dot leaders
Leaders are so widespread in print media that you simply is likely to be questioning, why doesn’t CSS already help that? The reply is: it does. Nicely, type of.
There may be really a chief()
perform outlined within the CSS Generated Content material for Paged Media specification. Nonetheless, as with a lot of the paged media specs, this perform isn’t applied in any browsers, subsequently excluding it as an possibility (not less than on the time I’m penning this). It’s not even listed on caniuse.com, presumably as a result of nobody has applied it and there aren’t any plans or indicators that they may.
Thankfully, each Julie and Christoph already addressed this drawback of their respective posts. To insert the dot leaders, they each used a ::after
pseudo-element with its content material
property set to a really lengthy string of dots, like this:
.toc-list li > a > .title {
place: relative;
overflow: hidden;
}
.toc-list li > a .title::after {
place: absolute;
padding-left: .25ch;
content material: " . . . . . . . . . . . . . . . . . . . "
". . . . . . . . . . . . . . . . . . . . . . . "
". . . . . . . . . . . . . . . . . . . . . . . "
". . . . . . . . . . . . . . . . . . . . . . . "
". . . . . . . . . . . . . . . . . . . . . . . "
". . . . . . . . . . . . . . . . . . . . . . . "
". . . . . . . . . . . . . . . . . . . . . . . ";
text-align: proper;
}
The ::after
pseudo-element is ready to an absolute place to take it out of the stream of the web page and keep away from wrapping to different traces. The textual content is aligned to the appropriate as a result of we wish the final dots of every line flush to the quantity on the finish of the road. (Extra on the complexities of this later.) The .title
aspect is ready to have a relative place so the ::after
pseudo-element doesn’t get away of its field. In the meantime, the overflow
is hidden so all these additional dots invisible. The result’s a fairly desk of contents with dot leaders.
Nonetheless, there’s one thing else that wants consideration.
Sara additionally identified to me that each one of these dots depend as textual content to display screen readers. So what do you hear? “Introduction dot dot dot dot…” till all the dots are introduced. That’s an terrible expertise for display screen reader customers.
The answer is to insert an extra aspect with aria-hidden
set to true
after which use that aspect to insert the dots. So the HTML turns into:
<ol class="toc-list" function="listing">
<li>
<a href="#link_to_heading">
<span class="title">Chapter or subsection title<span class="leaders" aria-hidden="true"></span></span>
<span class="web page"><span class="visually-hidden">Web page</span> 1</span>
</a>
<ol function="listing">
<!-- subsection gadgets -->
</ol>
</li>
</ol>
And the CSS turns into:
.toc-list li > a > .title {
place: relative;
overflow: hidden;
}
.toc-list li > a .leaders::after {
place: absolute;
padding-left: .25ch;
content material: " . . . . . . . . . . . . . . . . . . . "
". . . . . . . . . . . . . . . . . . . . . . . "
". . . . . . . . . . . . . . . . . . . . . . . "
". . . . . . . . . . . . . . . . . . . . . . . "
". . . . . . . . . . . . . . . . . . . . . . . "
". . . . . . . . . . . . . . . . . . . . . . . "
". . . . . . . . . . . . . . . . . . . . . . . ";
text-align: proper;
}
Now display screen readers will ignore the dots and spare customers the frustration of listening to a number of dots being introduced.
Ending touches
At this level, the desk of contents part appears fairly good, nevertheless it might use some minor element work. To begin, most books visually offset chapter titles from subsection titles, so I made the top-level gadgets daring and launched a margin to separate subsections from the chapters that adopted:
.toc-list > li > a {
font-weight: daring;
margin-block-start: 1em;
}
Subsequent, I needed to wash up the alignment of the web page numbers. The whole lot seemed okay once I was utilizing a fixed-width font, however for variable-width fonts, the chief dots might find yourself forming a zigzag sample as they alter to the width of a web page quantity. For example, any web page quantity with a 1 can be narrower than others, leading to chief dots which might be misaligned with the dots on earlier or following traces.
To repair this drawback, I set font-variant-numeric
to tabular-nums
so all numbers are handled with the identical width. By additionally setting the minimal width to 2ch
, I ensured that each one numbers with one or two digits are completely aligned. (You could wish to set this to 3ch
in case your undertaking has greater than 100 pages.) Right here is the ultimate CSS for the web page quantity:
.toc-list li > a > .web page {
min-width: 2ch;
font-variant-numeric: tabular-nums;
text-align: proper;
}
And with that, the desk of contents is full!
Conclusion
Making a desk of contents with nothing however HTML and CSS was extra of a problem than I anticipated, however I’m very pleased with the consequence. Not solely is that this strategy versatile sufficient to accommodate chapters and subsections, nevertheless it handles sub-subsections properly with out updating the CSS. The general strategy works on net pages the place you wish to hyperlink to the varied places of content material, in addition to PDFs the place you need the desk of contents to hyperlink to completely different pages. And naturally, it additionally appears nice in print when you’re ever inclined to make use of it in a brochure or e-book.
I’d wish to thank Julie Blanc and Christoph Grabo for his or her wonderful weblog posts on making a desk of contents, as each of these have been invaluable once I was getting began. I’d additionally wish to thank Sara Soueidan for her accessibility suggestions as I labored on this undertaking.