Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Statamic Anchor Navigation Laravel Package

visuellverstehen/statamic-anchor-navigation

Statamic addon that automatically generates anchor navigation from your content headings, adding linked IDs and a table of contents so visitors can jump between sections. Configurable heading levels make it ideal for long-form Bard/Replicator pages.

View on GitHub
Deep Wiki
Context7

Getting Started

visuellverstehen/statamic-anchor-navigation is a Statamic addon that scans long-form content (Bard, Replicator, Markdown) for headings, injects slugged id attributes into them, and renders a matching anchor navigation (table of contents) as a nested list. Day-to-day, it saves you from hand-maintaining a TOC on every article, documentation, or landing page entry.

Minimal steps:

  1. composer require visuellverstehen/statamic-anchor-navigation
  2. Confirm your compatibility row — v1.1.0 supports Statamic 5 and 6. Statamic 4 is no longer supported (see Gotchas).
  3. No publish step is required for basic use; the tag works out of the box with sensible defaults.
  4. Add the tag to the partial that renders your article body, typically once above the content and once wherever the headings live:
<article>
    <nav aria-label="On this page">
        {{ anchor_navigation }}
    </nav>

    <div class="prose">
        {{ content }}
    </div>
</article>

First use case to validate: a single content Bard field on a pages collection entry with 3–5 h2s. Render it, view source, and confirm each heading gained an id and each list item links to it. That five-minute check tells you whether the heading levels, ID strategy, and markup match your front-end conventions before you roll it into layouts.

Where to look first, in order: the README's tag parameter table, then config/anchor-navigation.php (publish it if you need to change heading levels, ID prefix, or wrapper classes), then the addon's Antlers view if you need to restructure the markup.

Implementation Patterns

TOC-above-content (default pattern). Render the navigation in the same partial as the field it scans so both stay in sync when editors reorder headings. Keep the tag call adjacent to {{ content }} rather than in a distant layout — it is far easier to reason about which field is being scanned and in what scope.

Restricting heading levels. Long articles often only want h2/h3 in the navigation and want h4 to stay out. Configure this centrally in the published config rather than per-call, unless you have genuinely different content types needing different depths (docs vs. blog). If you do need per-call control, prefer passing the levels explicitly at the call site and leaving the config as the site-wide default.

Sticky sidebar TOC for docs. Docs layouts benefit from a two-column shell where the TOC sits in a position: sticky aside. Combine with scroll-margin-top (see Gotchas) and an optional client-side scrollspy for active-section highlighting:

<div class="docs-grid">
    <aside class="docs-toc">
        {{ anchor_navigation }}
    </aside>
    <div class="prose">{{ content }}</div>
</div>
// Alpine scrollspy — no addon support needed, purely presentational
Alpine.data('scrollspy', () => ({
    active: null,
    init() {
        const obs = new IntersectionObserver((entries) => {
            entries.forEach(e => { if (e.isIntersecting) this.active = e.target.id })
        }, { rootMargin: '-80px 0px -70% 0px' })
        this.$el.querySelectorAll('.prose h2, .prose h3').forEach(h => obs.observe(h))
    }
}))

Bard and Markdown parity. Because the addon works off rendered heading markup, the same template code serves both a Bard field and a Markdown field — a real advantage when migrating content. Test both paths if your site mixes them; Markdown headings render slightly differently (attributes, wrapping) and that can affect slugging edge cases.

Replicator and multi-section pages. If each Replicator set renders its own body of content, generate a TOC per set inside the set's partial rather than one global TOC, otherwise IDs from sibling sets can collide and the navigation will link to the wrong section.

Multiple TOCs on one page. For landing pages with several independent content blocks, prefix IDs per block (via the config's ID prefix, or by wrapping and letting the addon scope to the container) so #introduction in block A doesn't shadow #introduction in block B.

Caching. The navigation is generated at render time, so the usual Statamic caching rules apply: it invalidates with the entry, and it is safe under static caching because the output is deterministic. Do not reach for a JS-only TOC to "fix" caching — you lose the editor preview and the accessibility of server-rendered links.

Gotchas and Tips

Statamic 4 is dropped in v1.1.0. This is the only real upgrade risk in this release. If you are on Statamic 4, stay pinned: composer require "visuellverstehen/statamic-anchor-navigation:^1.0". If you are on Statamic 5, v1.1.0 is a drop-in — the release adds Statamic 6 support and tests, and does not change the public API. Plan the 4 → 5 upgrade before taking v1.1.x.

Statamic 6 pulls the platform forward. Supporting Statamic 6 means the upper bound of your dependency graph moves with it (Laravel 12 / newer PHP). Check composer why-not before assuming your other Statamic addons are ready; anchor navigation is rarely the blocker, but it is often the addon that surfaces it because it is small and upgrades fast.

Duplicate heading text produces duplicate IDs. Two ## Overview headings on one page will collide. Decide on a convention (unique headings, or a numbering scheme in the heading) and enforce it editorially — slug de-duplication behaviour is the first thing to verify if a TOC link jumps to the wrong place.

Fixed headers eat your anchors. This is the single most common complaint with anchor navigation and it is a CSS problem, not a package problem:

.prose :is(h2, h3, h4) { scroll-margin-top: 6rem; }

Set it to your header height plus breathing room, and include it in the base stylesheet so new templates inherit it.

IDs may already exist. Bard sets with embedded HTML, or other addons that add heading IDs, can produce conflicting or pre-existing id attributes. If a heading already has an ID, check whether the addon preserves or overwrites it before you rely on it — and avoid mixing two heading-ID solutions on one page.

Headings inside collapsed UI won't scroll into view. If a heading lives inside an accordion or tab set, the anchor will update the URL but nothing appears to happen. Either exclude those sets from the navigation or add a small handler that opens the containing set on hashchange.

Live preview and the CP. Because the navigation depends on the rendered field, it will appear in live preview but not in the entry editor's field display — don't file that as a bug, and don't promise editors a TOC preview they won't get.

Customise markup via the view, not string munging. Publish the addon's view and edit the list structure directly if you need aria-current, custom classes, or a heading on the <nav>. Overriding with regex or DOM manipulation in a modifier is fragile and breaks on the next release.

Tests now exist in the package — use them as a signal. v1.1.0 adds a test suite and CI. If you are extending the addon (custom view, custom config), keep a small feature test in your own app that asserts the expected ids and link targets appear in a rendered entry. That is your regression net for the next Statamic major.

Accessibility checklist. Wrap in <nav aria-label="On this page"> (or Table of contents), keep the list nested to mirror heading depth, and don't suppress the navigation when only one heading exists — it's noise then, so gate it with a count check if the addon exposes one, or accept the minor clutter.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route