Anchor and waves

Anchors for headings in MDX

How to add anchors to headings in MDX? It's surprisingly easy to do. I assume you are already familiar with MDX so somewhere in your code you should have a blog page layout component that uses <MDXProvider>, like that:

<MDXProvider>{children}</MDXProvider>

MDX consists of components itself. So if there is a code like this then the interpreter changes the inner value (children) into the same content but wrapped with <code></code>.

The same applies to all the headings, lists, paragraphs, etc. Our job is to override the headings. We can do it by passing components hash and specifying a replacement.

import H2 from './MyCustomMDX/H2';
import H3 from './MyCustomMDX/H3';
import H4 from './MyCustomMDX/H4';
// ...
<MDXProvider
components={{
h2: H2,
h3: H3,
h4: H4,
}}
>
{children}
</MDXProvider>;

Please notice that we are not going to add an anchor to the <h1> tag. It doesn't make sense in my opinion. <h1> is like a summary of the whole page. The URL that links to it is the direct link to the post. Anchors should be used to specific parts of a post (to a section).

Override heading component

The override for <h2> that shows an anchor when the mouse is over the text could look like this:

1// ./MyCustomMDX/H2.js
2
3function getAnchor(text) {
4 return text
5 .toLowerCase()
6 .replace(/[^a-z0-9 ]/g, '')
7 .replace(/[ ]/g, '-');
8}
9
10const H2 = ({ children }) => {
11 const anchor = getAnchor(children);
12 const link = `#${anchor}`;
13 return (
14 <h2 id={anchor}>
15 <a href={link} className="anchor-link">
16 §
17 </a>
18 {children}
19 </h2>
20 );
21};
22
23export default H2;
24
Available onAvailable on CodeSandbox

Below you'll see the demo. Please hover over the text. On the left you should see § sign that is also a link, representing our anchor:

I'm h2 with an anchor

Let's explain a few bits. The way we use headings in Markdown is by using # sign, for example:

## I'm h2 with an anchor

Everything that goes after ##  is passed as a child to the H2 component.

So the next interesting bit is done in the getAnchor function. Take a look at lines 3 to 8. This is what happens:

  • line 5 - we convert the input to lower case "i'm h2 with an anchor"
  • line 6 - we remove all non-alphanumeric characters "im h2 with an anchor"
  • line 7 - we replace spaces with a hyphen "im-h2-with-an-anchor"

... and voilà. We have a URL-friendly anchor 🎉

The styling

Another important thing here is the CSS. We want to show the anchor only on hover and somewhere next to the heading itself:

h2 {
position: relative;
}
.anchor-link {
color: #666;
opacity: 0;
position: absolute;
transform: translate(-1em, -2px);
width: 1em;
}
h2:hover .anchor-link {
opacity: 1;
}

Of course, you can go crazy with your anchors ;) That one is very basic.

Recommendation

One thing that is easy to overlook here (in my example) is using a character like § inside of <h2> tag. In this approach, the sign will become a part of the document outline. Which is not something we want. It's better to use an icon in SVG format for example but I didn't want to complicate the example.

If the simple sign is what you want then you should render <a> tag before or after the <h2>.