Section Rail

A responsive section navigation rail with scroll tracking, smooth anchor jumps, link mode, and optional rich hover previews.

A calmer table of contents

Long-form pages should stay readable without making navigation feel heavy. The rail turns the document outline into a quiet waveform at the edge of the page.

  • hover a mark to reveal its label
  • select one to jump to that section
  • scroll to watch the active mark travel

Structure

Each rail item points to an element with the same id. The content can come from Markdown, MDX, a CMS, or ordinary React components.

Keep section IDs unique when more than one document appears on a page.

Scroll behavior

An intersection observer follows the section nearest the reading band. Selecting a mark scrolls smoothly, while reduced motion preferences switch the jump to instant scrolling.

The contained variant can track an overflow panel like this preview instead of the browser viewport.

Ready to publish

Use the default viewport variant for articles and documentation pages. Use variant="contained" with a scrollRootRef for dialogs, side panels, and component previews.

That is the whole pattern: stable IDs, useful labels, and content worth navigating.

Installation

Section Rail is published as a shadcn registry item. The CLI drops the file into your project, installs its dependencies, and adds any primitives it relies on.

$npx shadcn@latest add @akoder/section-rail

First time? Register the @akoder namespace once in components.json and every component installs by name.

"registries": { "@akoder": "https://akoder.xyz/r/{name}.json" }

Usage

Give each rail item the id of its target section. Without an href, the rail follows the reading position and scrolls to sections when selected.

"use client";
 
import { SectionRail, type RailItem } from "@/components/section-rail";
 
const items: RailItem[] = [
  { id: "overview", label: "overview" },
  { id: "installation", label: "installation" },
  { id: "api", label: "api" },
];
 
export function Article() {
  return (
    <>
      <SectionRail items={items} />
      <article>
        <section id="overview">...</section>
        <section id="installation">...</section>
        <section id="api">...</section>
      </article>
    </>
  );
}

Contained scroll areas

Use the contained variant for a dialog, drawer, or overflow panel. The rail stays attached to the surrounding relative container while observing and scrolling the referenced element.

"use client";
 
import { useRef } from "react";
import ReactMarkdown from "react-markdown";
import { SectionRail } from "@/components/section-rail";
 
const sections = [
  { id: "intro", label: "intro", body: "## Introduction\n\nStart here." },
  { id: "details", label: "details", body: "## Details\n\nKeep reading." },
];
 
export function MarkdownGuide() {
  const scrollRootRef = useRef<HTMLDivElement>(null);
 
  return (
    <div className="relative">
      <SectionRail
        items={sections}
        variant="contained"
        scrollRootRef={scrollRootRef}
      />
      <div ref={scrollRootRef} className="h-96 overflow-y-auto pl-20">
        {sections.map((section) => (
          <section key={section.id} id={section.id} className="min-h-96">
            <ReactMarkdown>{section.body}</ReactMarkdown>
          </section>
        ))}
      </div>
    </div>
  );
}

Link rail

Items with an href navigate instead of scrolling. Pass activeId to pin the current page.

<SectionRail
  activeId="guides"
  items={[
    { id: "home", label: "home", href: "/" },
    { id: "guides", label: "guides", href: "/guides" },
  ]}
/>

Props

PropTypeDefaultNotes
itemsRailItem[]requiredOrdered marks. Each item needs id and label; add href for navigation or card for a rich tooltip.
activeIdstringPins the active mark. Most useful for link rails.
variant"viewport" | "contained""viewport"Viewport rails are fixed and appear at the lg breakpoint; contained rails are absolutely positioned and always visible.
scrollRootRefRefObject<HTMLElement | null>Overflow element that owns the target sections in contained mode.
classNamestringAdditional classes for positioning or styling the rail.

RailItem

FieldTypeNotes
idstringUnique item key and the target element ID for scroll items.
labelstringAccessible name and default tooltip copy.
hrefstringTurns the mark into a Next.js link.
card{ title; description?; icon? }Replaces the plain tooltip with a rich preview card.

Notes & features

Manual installation

Copy components/section-rail.tsx into your project. Install the shadcn tooltip primitive and ensure the standard @/lib/utils cn helper is available. The link mode uses Next.js Link; replace it with an anchor or your router's link component outside Next.js.