Series Navigation

Add prev/next navigation for multi-part tutorials and series

3 min read 514 words

Add navigation for multi-part content like tutorial series, allowing readers to move between parts.

Note

Built into Default Theme

Bengal's default theme includes navigation components:

  • Prev/Next navigation via{{ page_navigation(page) }}macro
  • Section-scoped for docs/tutorials (respectsweightorder)
  • Track navigation for learning paths with progress bars
  • Enable withnavigation.prev_nextfeature flag

This recipe shows how to usepage.seriesfrontmatter for explicit multi-part series with progress tracking.

The Pattern

Frontmatter Setup

Each page in the series needs series metadata:

---
title: "Part 2: Building Components"
series:
  name: "React from Scratch"
  part: 2
  total: 5
---

Template Code

{% if page.series %}
<nav class="series-nav">
  <div class="series-header">
    <span class="series-name">{{ page.series.name }}</span>
    <span class="series-progress">Part {{ page.series.part }} of {{ page.series.total }}</span>
  </div>

  <div class="series-links">
    {% if page.prev_in_series %}
    <a href="{{ page.prev_in_series.href }}" class="prev">
      ← {{ page.prev_in_series.title }}
    </a>
    {% end %}

    {% if page.next_in_series %}
    <a href="{{ page.next_in_series.href }}" class="next">
      {{ page.next_in_series.title }} →
    </a>
    {% end %}
  </div>
</nav>
{% end %}

What's Happening

Component Purpose
page.series Series object withname,part,total
page.prev_in_series Previous Page object (or None if first)
page.next_in_series Next Page object (or None if last)

Variations

{% if page.series %}
<div class="series-progress-bar">
  <div class="progress" style="width: {{ (page.series.part / page.series.total * 100) | round }}%"></div>
</div>
<span>{{ page.series.part }} / {{ page.series.total }}</span>
{% end %}

Show all parts with current highlighted:

{% if page.series %}
<aside class="series-toc">
  <h4>{{ page.series.name }}</h4>
  <ol>
    {% let series_pages = page._section.pages | sort_by('weight') %}
    {% for part_page in series_pages %}
    {% if part_page.series and part_page.series.name == page.series.name %}
    <li {% if part_page.eq(page) %}class="current"{% end %}>
      <a href="{{ part_page.href }}">{{ part_page.title }}</a>
    </li>
    {% end %}
    {% end %}
  </ol>
</aside>
{% end %}

Note: This assumes all parts of the series are in the same section. For series spanning multiple sections, usesite.indexes.series.get(page.series.name) | resolve_pagesinstead.

{% if page.series %}
<footer class="series-footer">
  {% if page.prev_in_series %}
  <a href="{{ page.prev_in_series.href }}">← Previous</a>
  {% else %}
  <span></span>
  {% end %}

  <span>{{ page.series.part }}/{{ page.series.total }}</span>

  {% if page.next_in_series %}
  <a href="{{ page.next_in_series.href }}">Next →</a>
  {% else %}
  <span></span>
  {% end %}
</footer>
{% end %}
{% if page.series %}
  {% if not page.next_in_series %}
  <div class="series-complete">
    🎉 You've completed "{{ page.series.name }}"!
  </div>
  {% end %}
{% end %}

Example CSS

.series-nav {
  border: 1px solid var(--border-color);
  border-radius: 8px;
  padding: 1rem;
  margin: 2rem 0;
}

.series-header {
  display: flex;
  justify-content: space-between;
  margin-bottom: 1rem;
  font-size: 0.875rem;
  color: var(--text-muted);
}

.series-links {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
}

.series-links a {
  flex: 1;
  padding: 0.75rem;
  background: var(--bg-secondary);
  border-radius: 4px;
  text-decoration: none;
}

.series-links .prev { text-align: left; }
.series-links .next { text-align: right; }

Seealso