How to add a table of contents to a Framer blog

A table of contents makes long posts scannable and gives crawlers a clean map of your page. Here is how to structure headings so one can be generated automatically.

Once your posts get past about eight hundred words, a table of contents stops being decoration. It tells a visitor whether the answer they want is on this page before they commit to scrolling, and it gives search engines and AI crawlers a clean outline of what the page covers.

The part people skip is that a good table of contents is mostly a heading problem, not a component problem.

Get the heading structure right first

One h1 per page, and it should be the post title. Section headings are h2. Sub-points inside a section are h3. That is the whole rule, and almost every messy table of contents comes from breaking it — usually by using a heading level because of how big the text looks rather than what it means.

In Framer, heading size comes from your text style presets, so you can style an h2 to look small without demoting it to h3. Choose the tag for meaning and the preset for appearance.

Write headings that stand alone

A heading reading "Why it matters" is useless in a contents list stripped of context. "Why animated backgrounds slow a page down" works both in place and out of it. This also happens to be what AI answer engines quote when they cite a section.

Generate it, do not maintain it

A hand-written contents list goes stale the first time you edit a post and forget to update it. The reliable approach is to scan the headings already rendered on the page and build the list from them, so it stays correct no matter how the post changes.

Scope that scan to the article body rather than the whole document. If it reads every heading on the page it will pick up your navigation, your newsletter section and your footer, and the list becomes noise.

The behaviour worth having

Three things earn their keep. Scroll highlighting, so the visitor can see where they are in a long piece. Sticky positioning on wide screens, so the list stays reachable. And a collapsed state on mobile, because a fixed sidebar on a phone costs more than it gives.

Everything past that is usually noise. Numbering, nested third levels and animated indicators all sound good and mostly add clutter.

One technical warning

If you build this yourself, be careful with observers. A contents component that watches the page for changes, and whose own rendering counts as a change, will retrigger itself forever. Ignore mutations inside your own subtree, or scope the observer tightly enough that it never sees its own output.

That loop is silent — the page still works, it just burns CPU quietly — which is exactly why it is worth checking for deliberately.

Asset updates

Get new Framer assets delivered to your inbox

New releases, free assets, and occasional offers. No spam.

Asset updates

Get new Framer assets delivered to your inbox

New releases, free assets, and occasional offers. No spam.