DEV Community

Cover image for Anchors for headings in MDX
🤓 Tomek Nieżurawski
🤓 Tomek Nieżurawski

Posted on • Originally published at tomekdev.com on

1 1

Anchors for headings in MDX

This post was originally published on https://tomekdev.com/posts/anchors-for-headings-in-mdx. What you see as GIF here is interactive there. ✌️


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>
Enter fullscreen mode Exit fullscreen mode

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>;
Enter fullscreen mode Exit fullscreen mode

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:

// ./MyCustomMDX/H2.js

function getAnchor(text) {
  return text
    .toLowerCase()
    .replace(/[^a-z0-9]/g, '')
    .replace(/[]/g, '-');
}

const H2 = ({ children }) => {
  const anchor = getAnchor(children);
  const link = `#${anchor}`;
  return (
    <h2 id={anchor}>
      <a href={link} className="anchor-link">
        §
      </a>
      {children}
    </h2>
  );
};

export default H2;
Enter fullscreen mode Exit fullscreen mode

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

Anchor demo

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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 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>.

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

SurveyJS custom survey software

JavaScript Form Builder UI Component

Generate dynamic JSON-driven forms directly in your JavaScript app (Angular, React, Vue.js, jQuery) with a fully customizable drag-and-drop form builder. Easily integrate with any backend system and retain full ownership over your data, with no user or form submission limits.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay