Anchored Headings for Portable Text
How to automatically generate linked headings with a custom serializer
By Knut Melvær
serializers.js
import PortableText from "@sanity/block-content-to-react";
export const serializers = {
type: {
block: (props) => {
const { node, children } = props
const { style, _key } = node
if (/^h\d/.test(style)) {
const HeadingTag = style;
// Even though HTML5 allows id to start with a digit, we append it with a letter to avoid various JS methods to act up and make problems
const headingId = `h${_key}`;
return (
<HeadingTag id={headingId}>
<a
href={`#${headingId}`}
aria-hidden="true"
tabIndex={-1}
>#</a>
<span>{children}</span>
</HeadingTag>
)
}
// ... you can put in other overrides here
// or return the default ones 👇
return PortableText.defaultSerializers.types.block(props)
}
},
// more custom types here…
}This is a minimal example of how you can add extra attributes and markup to headings from Portable Text in React. You'd approach this similarly in other frameworks. Here we have a custom serializer for the block type. It looks after a style property that contains the pattern with the letter h and a digit (so h1, h2, h3, etc).
Then we generate an id from the _key that comes from the block data. This _key will be stable as long as the block (that is, the heading) exists. Alternatively, you can generate a readable id from the heading text data in children.
This markup put's a linked # inside of the heading, but you can of course do it however you want and add styling to it.
Contributor

Knut Melvær
Knut is a principal developer marketing manager at Sanity.io
Norway