Doing Section Links “right”
2 July 2023
What are Section Links?
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?
- “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.
- “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.
- “a inside h” - insert an anchor tag immediately before the
header text, with some dummy content like
#
or something. - “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.
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:
- They require JS, when a HTML-only solution would do.
- Users can already right-click and “copy to clipboard” in chrome and firefox.
- You can’t click them to snap the desired section into view.