César Ferradas

Deep anchor links in 13 lines of plain JavaScript

02 July 2024 (updated 03 July 2024)

Adding clickable “deep anchor links” to the headings of any page of your website is very simple and can be done with very little code.

We’ll be adding a clickable "#" icon at the end of each heading, which adds the id of that heading to the URL, allowing the user to copy the URL to share a deep link to that particular heading on your page.

This simulates the behaviour of tools like AnchorJS, but AnchorJS is 5.67 KB in minified size, and the script I will show you is just 338 bytes, unminified.

Here’s a demo:

Demo

The only prerequisite for this to work is that the heading tags should already have id properties on them. For example:

<h3 id="my-subsection">My subsection</h3>

This is often done automatically by blogging platforms like Jekyll.

Once your headings have ids on them, you can add this JS script to your site:

(function() {
  var headings = document.querySelectorAll("h2,h3");
  for (var heading of headings) {
    var l = document.createElement("a");
    l.setAttribute("href", "#" + heading.id);
    l.textContent = "#";
    l.className = "anchor";
    l.style = "padding-left: 8px; text-decoration: none;";
    heading.appendChild(l);
  }
})();

The script only adds <a> tags to <h2> and <h3> tags. The reasoning for this is that the <h1> tag should be the title of the page, which is at the top already. Why would you deep link to that? As for <h4> and below, I personally don’t think linking to such a deep level is worth it.

But if you’d still like to link deeper, you can simply add more tags:

-  var headings = document.querySelectorAll("h2,h3");
+  var headings = document.querySelectorAll("h2,h3,h4,h5");

Optionally, if you’d like to hide the # icon until someone hovers on the headings, you can achieve this with some CSS:

a.anchor {
  opacity: 0;
}

*:hover>.anchor, a.anchor:hover {
  opacity: 1;
}

That’s all you really need. AnchorJS is great if you don’t already have ids on your headings, if you need to customise the icon, or if you want to link to parts of the page that are not headings. But for most sites I think the above tiny script should do 90% of the job.

Accessibility update

After publishing this post I realised that the above solution is not accessible: screen readers pick up and read out the # at the end of each title.

For a full explanation on how to remedy this, have a look at Are your Anchor Links Accessible?, but in short, below is an improved script. It adds a <span> wrapper around the link and gives it an aria-hidden="true" property (we can’t add this property directly on the link because it’s a focusable element).

(function() {
  var headings = document.querySelectorAll("h2,h3");
  for (var heading of headings) {
    var s = document.createElement("span");
    s.className = "anchor";
    s.style = "padding-left: 8px;";
    s.setAttribute("aria-hidden", "true");
    var l = document.createElement("a");
    l.setAttribute("href", "#" + heading.id);
    l.textContent = "#";
    s.appendChild(l); heading.appendChild(s);
  }
})();

The script is no longer 10 lines, so I’ve updated the blog post’s title to a less snappy “in 13 lines”!