All writing From the journal

Building Effortless Document Outlines: How titleNav.js Generates Dynamic Navigation from Article Headings

Cover Image

Stop manually building table-of-contents widgets. Here is how a tiny, plug-and-play script transforms static document headings into an interactive, scroll-aware navigation system.


Technical documentation, API references, and product showcase pages have one thing in common: they are long.

When an article spans thousands of words across numerous <section> blocks and subheadings, navigating to a specific topic becomes tedious. Forcing readers to manually scroll through screens of dense text degrades user experience.

In-page heading navigation solves this friction by providing a table of contents that tracks reading progress in real time and lets readers jump straight to whatever section they need. While developers often write throwaway scroll-tracking scripts for this, recurring needs call for a clean, reusable component.

That is where titleNav.js comes in—a lightweight, plug-and-play jQuery plugin designed to parse heading tags, build an accessible navigation menu, maintain smooth scroll animations, and seamlessly manage active states.


1. Quick Start: Single-Line Automatic Sidebar Generation

The core philosophy behind titleNav.js is zero-friction implementation. Instead of requiring complex configuration schemas or manual HTML markup, the plugin queries your document’s semantic heading elements (such as <h3>), dynamically creates the navigation DOM container, and establishes scroll listeners.

To create a functional floating sidebar table of contents, initialize the plugin against your heading selector and provide standard matching styles for the generated navigation container (.title-nav-ul) and links (.title-nav-li):

<!-- Step 1: Initialize the plugin with a single line of jQuery -->
<script>
  $('h3').titleNav();
</script>

<!-- Step 2: Add essential positioning and highlight styling -->
<style>
.title-nav-ul {
  width: 200px;
  position: absolute;
  top: 160px;
  right: 16px;
  background-color: #ffffff;
  border: 1px solid #e2e8f0;
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
  z-index: 50;
}

.title-nav-li {
  display: block;
  padding: 10px 14px;
  font-size: 13px;
  color: #334155;
  text-decoration: none;
  border-bottom: 1px solid #f1f5f9;
  transition: all 0.15s ease;
}

.title-nav-li:hover {
  background-color: #f8fafc;
}

.title-nav-li.active {
  font-weight: 700;
  color: #0284c7;
  background-color: #f0f9ff;
  border-left: 3px solid #0284c7;
}
</style>

When rendered, the plugin extracts each heading’s text into an anchor element. As the reader moves past the top threshold, the menu transitions dynamically from relative positioning to a fixed overlay, ensuring the table of contents remains accessible throughout the reading session.


2. Adapting the Layout: Sticky Horizontal Header Navigation

Sidebar navigation works well on wide desktop screens, but editorial layouts or dashboard panels often demand horizontal navigation that stays pinned to the top of the reading container.

Because titleNav.js decouples logic from presentation, you can attach the plugin to a pre-existing markup container and restyle it using standard CSS Flexbox:

/* Layout override: Transform list into a sticky horizontal top bar */
.title-nav-ul.horizontal {
  position: sticky;
  top: 0;
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  background-color: #0f172a;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  z-index: 60;
}

.title-nav-ul.horizontal .title-nav-li {
  display: inline-flex;
  align-items: center;
  padding: 12px 20px;
  color: #94a3b8;
  border-bottom: 2px solid transparent;
}

.title-nav-ul.horizontal .title-nav-li.active {
  color: #38bdf8;
  font-weight: 600;
  background: transparent;
  border-bottom-color: #38bdf8;
}
// Target existing nav container and bind to local scroll viewport
$('#horizontal-article h3').titleNav({
  nav: $('#horizontal-nav-bar'),
  container: $('#demo-viewport'),
  offsetTop: 0
});

By passing an existing jQuery element to the nav property, titleNav.js populates the links without altering the parent layout. This lets you nest navigation bars inside sticky application headers, modal windows, or split panes without writing custom mounting logic.


3. Scoped Viewports and Scroll Offsets

In real-world web applications, documents rarely scroll solely via window. Often, articles live inside dedicated scrollable containers like modal dialogs, tabbed panels, or drawer viewports.

To support these structures, titleNav.js provides granular configuration options for view boundaries and activation triggers:

OptionTypeDefaultDescription
navjQuery Object | nullnullTarget element to receive navigation items. Defaults to null (auto-creates container).
containerjQuery Object$(window)The scrollable element to observe. Defaults to the window object.
offsetTopNumber20Distance (in pixels) from the top boundary where an anchor switches active state.

Here is how you configure the plugin to monitor a nested viewport with a custom trigger point:

// Monitor a scoped scroll pane with a 30px trigger offset
$('#split-pane-content h3').titleNav({
  container: $('#split-pane-content'),
  offsetTop: 30
});

Setting offsetTop ensures headings trigger their active state precisely as they align with visual boundary guides or sticky headers, preventing abrupt or premature active jumps.


4. Solving the “Short Last Section” Edge Case

A persistent flaw in standard scrollspy implementations involves short terminal sections. If an article’s concluding chapter or reference section is shorter than the viewport height, scrolling can hit the bottom of the page before the heading reaches the top trigger offset. Consequently, traditional scripts leave the second-to-last item highlighted, misrepresenting the reader’s actual position.

titleNav.js incorporates a dedicated boundary calculation inside its scroll event loop to prevent this issue:

// Excerpt: Edge-case handling inside titleNav.js
var scrollTop = $container.scrollTop();
var containerHeight = $container.height();
var scrollHeight = $container[0].scrollHeight;

// Boundary check: If the container is scrolled to the absolute bottom,
// immediately activate the final navigation item.
if (scrollTop + containerHeight >= scrollHeight - 8) {
  $navItems.removeClass('active');
  $navItems.last().addClass('active');
  return;
}

By comparing total scroll height with current scroll offset plus viewport height, the plugin guarantees that the final section receives its active status as soon as the reader reaches the bottom of the page—regardless of how short that section is.


Clean DOM Output

When inspecting the generated DOM, titleNav.js produces semantic and minimal markup:

<!-- Auto-generated navigation structure -->
<div class="title-nav-ul">
  <a href="javascript:" class="title-nav-li active">1. Why the name LuLu?</a>
  <a href="javascript:" class="title-nav-li">2. Why use LuLu?</a>
  <a href="javascript:" class="title-nav-li">3. Design philosophy</a>
  <a href="javascript:" class="title-nav-li">4. Highlight me when scrolling to bottom</a>
</div>

Clicks on any generated .title-nav-li link calculate the target heading’s exact offset within the container and execute smooth, native scrolling animations without requiring external animation dependencies.


Key Takeaways

  1. Automation Saves Time: Scanning semantic tags (h2, h3) eliminates manual maintenance of in-page navigation links as documentation evolves.
  2. Flexible Architecture: Whether styled as an absolute sidebar or a sticky flex bar, the plugin separates scroll tracking from visual presentation.
  3. Edge-Case Awareness: Built-in bottom-detection solves the common scrollspy pitfall where short ending sections fail to trigger.
  4. Lightweight & Cross-Browser: Clean, dependency-light vanilla/jQuery logic ensures broad compatibility across desktop browsers and touch devices alike.

“You are in the southern sun as snow flurries blow,
I am in the cold northern night where spring flowers grow.”
— Zhang Xinxu


Try It Yourself

Want to see these concepts in action? I’ve created an interactive demo where you can experiment with the code and see real-time results.

View the Live Demo

Explore more demos from my previous articles in the Demo Gallery.

Happy coding!

FOLLOW YOUR CURIOSITY

Find a good idea.

Search the complete journal in English or 中文.