Permalinks for Eleventy headings

Quite often I share URLs to a specific section of a web page. For code snippets, on the other hand, I tend to copy & paste. On mobile, the chore of selecting, copying and pasting text is a pain. For a recipient, snippet sharing also results in loss of context. A link would've been more helpful.

I'll describe how I use Eleventy, to handle links to sections and code snippets here on the site.

GitHub style headings #

The backstory for my choice of in-page permalink design, is inspiration from GitHub's direct links to README sections. I wanted to automate generation of IDs for headings and couple them with <a> tags. For blogs in Markdown, Eleventy can be extended with markdown-it-anchor(opens in a new window). I however, have blogs using both HTML and Markdown templates.

I created a feature request issue for Eleventy(opens in a new window), to find a source agnostic solution. The following discussion, provided guidance towards the approach I've taken.

Text and snippet headings #

11ty provides reusable functions for generating page content via shortcodes. Shortcodes can be tweaked to fit site specifics, and are usable in both Markdown and HTML templates. I aimed at creating headings for blog sections and code snippets, mimicking how headings are generated by markdown-it-anchor.

Eleventy shortcode #

js
module.exports = eleventyConfig => {
  // direct link's text
  const symbol = "#"
  const slug = eleventyConfig.getFilter("slug");
  eleventyConfig.addShortcode("h", (level, text, classList = "") => {
    const tag = `h${level}`;
    // URL safe heading IDs
    const id = slug(text).replace(/[^\w-]/g, "");
    return `
<${tag} id="${id}"${classList && ` class="${classList}"`}>
  <span class="heading">${text}</span>
  <a class="direct-link" href="#${id}">${symbol}</a>
</${tag}>`;
  });
};

The shortcode h declares parameters for heading level (e.g. <h2>) and text. The heading's text is used to generate IDs and associated anchor tags. Optionally, the shortcode also supports specifying additional CSS classes to style headings contextually.

Shortcode in action #

Regardless if a blog's template is HTML or Markdown, the shortcode is used the same way. Below follows an example for how the shortcode is called.

Use heading shortcode #

html
<div class="code-snippet__description">
  {% h 4 "Eleventy shortcode" "code-snippet__comment" %}
  <abbr title="JavaScript" class="code-snippet__lang">js</abbr>
</div>

I specify page headings to use flexbox, to enable styling based on page layout. The advantage of doing this, is that the heading's direct link's can be repositioned. To render the direct link before the heading text, the following CSS can be used.

css
/* reverse heading's element order */
h2 {
  display: flex;
  flex-direction: row-reverse;
  justify-content: flex-end;
}

Permalinks don't add any value if they aren't used. After all, the use case is navigation and sharing. Poorly chosen additional elements are noise. In contrast, without visible elements in-page navigation is hard to discover. In my opinion, (subtle) hints should always be visible for touch device users. For desktop users, the sweet spot is to show the links as the user tracks the page with their mouse or keyboard.

css
.direct-link:active,
.direct-link:hover,
.direct-link:focus {
  text-decoration: underline;
  opacity: 1;
}

:is(h2, h3, h4) > .direct-link {
  display: inline;
  opacity: 1;
  text-decoration: none;
  font-size: inherit;
  margin-left: 0.5rem;
}

:is(h2, h3, h4):active > .direct-link,
:is(h2, h3, h4):hover > .direct-link,
:is(h2, h3, h4):focus > .direct-link {
  display: inline;
  opacity: 1;
  text-decoration: none;
  font-size: inherit;
  margin-left: 0.5rem;
}

@media (hover: hover) {
  :is(h2, h3, h4) > .direct-link {
    display: none;
  }
}

In this blog, I've intentionally included direct links for all sections. Often, that's likely over the top. Keep your reader's experience top of mind, but do help them share your legwork.