Doing Section Links “right”

2 July 2023

By “section link”, I mean a document like this one which is broken into sections, and each section header contains a link to itself.

Why is this a good thing? For long documents, I find it really helpful to be able to link to the specific section I was reading. Not every site seems to use them - I saw them recently in the Cloudflare documentation, but Wikipedia doesn’t use them for example. Google has a “copy to clipboard” button, which I hate[1].

I’m using Pandoc, which already generates a table of contents for me, so I’d like to get away with only modifying the build process a little bit in order to change the markup. Anything marked as a header will have an ID automatically inserted into the document. I also wanted to avoid JS as much as possible.

You should be able to cheat by hovering over the section headers in this document to see what I’m going for.

How could you do it? Let me count the ways

Intuitively, there are a few ways this could work. Obviously, the markup has to change: we have to insert a new anchor somewhere in the document. But where exactly is the right place to put it?

  1. “a around h” - wrap the header tag in an anchor. This would make the entire header a link, extending all the way to the right. It would make selecting text from the header a little tricky.
  2. “h around a” - wrap the header’s inner content in an anchor tag. This would just make the text of the title into clickable text.
  3. “a inside h” - insert an anchor tag immediately before the header text, with some dummy content like # or something.
  4. “a next to h” - insert an anchor tag immediately before the header tag.

Option 4 was a non-starter for me, given my document layout. Placing an anchor immediately before the header tag would put it right after the previous section’s text, with nothing singling it out as “attached” to the header. This could be remedied by wrapping sections in a <section> tag, but I didn’t entertain it.

In pandoc, headers are considered Block elements and links are considerd Inline elements. Block elements may contain Inline elements, but not vice versa. Hence, option 1 was actually also a non-starter, despite being perfectly legitimate HTML.

Option 3 started to look attractive, because I really wanted a little “hashtag” just outside the main document flow that I could click. I figured that making that anchor element and pushing it outside (using position: absolute) was probably the simplest way to go.

The evils of unsemantic markup

I had some markup like this:

<h2 id="foo"> <a href="foo">#</a> Foo </h2>

I couldn’t actually use this, because it messed up my table of contents - every entry now started with #. “No problem”, I thought. “I’ll just style a ::before element with content: "#"”. The anchor would always be there, but the content would only become visible when you hovered over the title. That sort of worked, but then I noticed that tabbing through the document was really ugly. You’d gain focus on a zero-width element, and you’d never actually see the little hastag because it was only active on hover.

Then I started to think of screen readers - how would they even be able to tell what this link was for? Would they just encounter a blank link element called # with no meaning and get annoyed? I managed to find something relevant in the WCAG:

Success Criterion 2.4.4 Link Purpose (In Context) (Level A)

The purpose of each link can be determined from the link text alone or from the link text together with its programmatically determined link context, except where the purpose of the link would be ambiguous to users in general.

https://www.w3.org/TR/WCAG21/#link-purpose-in-context

programmatically determined link context

additional information that can be programmatically determined from relationships with a link, combined with the link text, and presented to users in different modalities

In HTML, information that is programmatically determinable from a link in English includes text that is in the same paragraph, list, or table cell as the link or in a table header cell that is associated with the table cell that contains the link.

https://www.w3.org/TR/WCAG21/#dfn-programmatically-determined-link-context

I felt pretty sure that for this anchor, in this case, the link context was “programmatically determined”. But I still wasn’t satisfied - the real link context is right there, why not use it? I still wanted the user to be able to highlight the title like normal text - was there some other way to guarantee that?

The “perfect” solution

I went back to my Pandoc filter and changed it back to option 2, wrapping the header text in a link tag. I tried out getting rid of the pointer for the header text, and removing the usual link styles.

h2 > * {
  text-decoration: none !important;
  cursor: text;
}

This didn’t quite disable the link, unforunately - if you clicked the header (with a text cursor) your page would snap to the section. However, tabbing through the document was a dream. I could navigate easily through the sections, and the part of the page with focus always made sense. And I got the warm fuzzy feeling inside that anyone could parse the meaning of the document without having to struggle.

I may change my mind later and decide that the headers should just behave like ordinary links, and be styled like other links in the page. But for now I’m happy with this solution.

If you want to see the code, feel free to take a look at the recent commits to the source repo for this site.

Addendum: What’s wrong with copy to clipboard?

What’s wrong with “copy to clipboard” buttons? I think they’re strictly worse than a simple anchor for several reasons: