No files matched your search
|
Before Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 47 KiB |
@@ -1,217 +0,0 @@
|
||||
---
|
||||
title: tree-style table of contents
|
||||
layout: post-with-toc.njk
|
||||
image:
|
||||
src: 2026/tree-styling-0.png
|
||||
alt: "screenshot of the table of contents for a blog post (this blog post, in fact). it's been styled so that the output resembles the hierarchical file system output from the shell program 'tree.'"
|
||||
tags:
|
||||
- reference
|
||||
- software
|
||||
---
|
||||
|
||||
I have a shiny new Table of Contents (hereafter ToC) section for some of my posts (like this one)! And for fun and profit, it's styled like the output of [`tree`](https://en.wikipedia.org/wiki/Tree_(command)). I want to lay out the CSS I used for this. Fair warning, there's some hyper-specific border widths and positioning that *probably* should be a `calc()` function of some kind.
|
||||
|
||||
I drew inspiration from this [StackOverflow issue regarding tree styling for lists](https://stackoverflow.com/questions/14423671/best-method-for-displaying-a-tree-list-using-css-only-no-images-or-js-example).
|
||||
|
||||
## `tree`
|
||||
|
||||
Just for reference, here's some sample `tree` output:
|
||||
|
||||
```txt
|
||||
.
|
||||
├── footer.njk
|
||||
├── head-content.njk
|
||||
├── header.njk
|
||||
├── layouts
|
||||
│ ├── base.njk
|
||||
│ ├── page-lists.njk
|
||||
│ ├── page.njk
|
||||
│ ├── post.njk
|
||||
│ ├── post-with-toc.njk
|
||||
│ └── tag-redirect.njk
|
||||
├── pagination.njk
|
||||
├── postlist.njk
|
||||
└── webring.njk
|
||||
```
|
||||
|
||||
(You'll note that there are gaps between the verticals of each line. That's due to the line-height on the page, but in a standard terminal those vertical lines would be contiguous.)
|
||||
|
||||
## HTML
|
||||
|
||||
Our ToC uses the following HTML structure:
|
||||
|
||||
```html
|
||||
<aside id="toc">
|
||||
<details>
|
||||
<summary><h2>table of contents</h2></summary>
|
||||
<nav>
|
||||
<ol>
|
||||
<li><a>h2 1</a></li>
|
||||
<li><a>h2 2</a>
|
||||
<ol>
|
||||
<li><a>h3 2a</a></li>
|
||||
<li><a>h3 2b</a></li>
|
||||
</ol>
|
||||
</li>
|
||||
<li><a>h2 3</a></li>
|
||||
<!-- etc -->
|
||||
</ol>
|
||||
</nav>
|
||||
</details>
|
||||
</aside>
|
||||
```
|
||||
|
||||
We can ignore the `<aside>`, `<details>`, `<summary>`, and `<nav>` components - we're going to focus on the `<ol>`s and `<li>`s.
|
||||
|
||||
## CSS
|
||||
|
||||
Despite referencing a StackOverflow issue above, I never like to just copy-paste a chunk of StackOverflow code into place. I don't like extraneous CSS, and sometimes there's stuff mixed in there that just isn't necessary. Let's build from the ground up.
|
||||
|
||||
### remove styling that will be in the way
|
||||
|
||||
By default, my lists have a lot of space around them for legibility when reading longer list items. We'll be accomplishing the vertical bars with borders here, so it's critical we not have any block-oriented spacing on `<ol>`s and `<li>`s. We're also going to take the numeric `list-style` off.
|
||||
|
||||
```css
|
||||
#toc ol,
|
||||
#toc li {
|
||||
margin-block: 0;
|
||||
list-style: none;
|
||||
}
|
||||
```
|
||||
|
||||
### start the structure with borders
|
||||
|
||||
The `tree`-like structure will be accomplished with both symbols (stolen directly from the tree output) and borders. Let's throw some borders on.
|
||||
|
||||
```css
|
||||
#toc li {
|
||||
border-left: .095rem solid var(--color-pink);
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
Those are super off. First, they're tabbed too far in to the left; second, there's *no* space between the border and the list item. We're going to fix the first thing by removing left margins on the `<ol>`s. The second is where we pull in the most critical piece of infrastructure.
|
||||
|
||||
```css
|
||||
#toc ol {
|
||||
margin-left: 0;
|
||||
}
|
||||
```
|
||||
|
||||
### `li::before`
|
||||
|
||||
We're going to use a character (technically two) from the `tree` output and place it before our list items.
|
||||
|
||||
```css
|
||||
#toc li::before {
|
||||
content: '├─' / "";
|
||||
color: var(--color-pink);
|
||||
}
|
||||
```
|
||||
|
||||
Notably, without additional positioning, the bars will be spaced away from the border. We want them to overlap perfectly.
|
||||
|
||||

|
||||
|
||||
```css
|
||||
#toc li::before {
|
||||
position: relative;
|
||||
left: -.34rem; /* I fiddled a lot and this is where I landed */
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
### adjust left margins
|
||||
|
||||
Right now, the vertical lines don't nest nicely underneath the level above. Let's change our margins. I also adjusted the line height very slightly, because I noticed on some levels of magnification that the tree line leader characters weren't actually meeting.
|
||||
|
||||
```css
|
||||
#toc li {
|
||||
margin-left: 1.5rem;
|
||||
line-height: 1.15;
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
### fix those pesky last children
|
||||
|
||||
You've probably noticed that the last children in the tree output had a bend rather than the T-shape we've been using. We'll want to remove the border from the last children and change the character we're using.
|
||||
|
||||
```css
|
||||
#toc li:last-child {
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
||||
#toc li:last-child::before {
|
||||
/* Technically, we could use this content for all of them, but doing the full
|
||||
bar for the non-last-childs keeps the border a *smidge* more consistent for
|
||||
different levels of magnification */
|
||||
content: '└─' / "";
|
||||
}
|
||||
```
|
||||
|
||||
With that, it starts to look complete!
|
||||
|
||||

|
||||
|
||||
### but what about really, really, really, really, REALLY long headers?
|
||||
|
||||
What about line wrapping? It looks pretty bad right now.
|
||||
|
||||

|
||||
|
||||
(And for the record, this happens for middle children too, it's not just a last child issue.)
|
||||
|
||||
We can fix this with a hanging indent.
|
||||
|
||||
```css
|
||||
#toc li {
|
||||
text-indent: 1.22rem hanging; /* Again, I fiddled */
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
## summary
|
||||
|
||||
Here's the final collected CSS:
|
||||
|
||||
```css
|
||||
#toc ol,
|
||||
#toc li {
|
||||
margin-block: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#toc ol {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
#toc li {
|
||||
border-left: .095rem solid var(--color-pink);
|
||||
margin-left: 1.5rem;
|
||||
line-height: 1.15;
|
||||
text-indent: 1.22rem hanging;
|
||||
}
|
||||
|
||||
#toc li::before {
|
||||
content: '├─' / "";
|
||||
color: var(--color-pink);
|
||||
position: relative;
|
||||
left: -.34rem;
|
||||
}
|
||||
|
||||
#toc li:last-child {
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
||||
#toc li:last-child::before {
|
||||
/* Technically, we could use this content for all of them, but doing the full
|
||||
bar for the non-last-childs keeps the border a *smidge* more consistent for
|
||||
different levels of magnification */
|
||||
content: '└─' / "";
|
||||
}
|
||||
```
|
||||