Jeremy Smith on May 20, 2015

Auto-generate navigation from page headers in Middleman

I’m working on a Middleman site right now that has one long page of content with a sidebar of navigation that links to the headers and sub-headers found on the page. (Underscore.js is a good example of this.) This is a common style for technical documentation sites these days.

I didn’t want to have to maintain the navigation content by hand. And since the navigation is meant to exactly reflect the header structure, I decided to write a navigation builder.

This is by no means a perfect solution, but it’s workable.

First, create a basic Middleman layout that loads a nav partial:

<!doctype html>
    <meta charset="utf-8">
    <title><%%= %></title>
    <%%= stylesheet_link_tag "all" %>
    <%%= javascript_include_tag  "all" %>

  <body class="<%%= page_classes %>">
    <div class="container">
          <%%= partial "nav" %>

        <%%= yield %>

Your nav partial is going to call the helper that will build your table of contents based on the content from your index.html.erb file:

<h2>Table of Contents</h2>

<%%= build_toc("index.html.erb") %>

Your index file will hold the content for your site, with headers and subheaders, and ids for each:

<h1>Site or page title</h1>

<h2 id="introduction">Introduction</h2>

<h3 id="first_subhead">First Subhead</h3>

<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>

<h3 id="second_subhead">Second Subhead</h3>

<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>

Now, create a helpers folder in you middleman project’s main folder and add the following navigation_helpers.rb file:

require 'nokogiri'

module NavigationHelpers

  def build_toc(file)

  class TOCBuilder
    include Padrino::Helpers::OutputHelpers
    include Padrino::Helpers::TagHelpers
    include Padrino::Helpers::AssetTagHelpers

    attr_reader :file

    def initialize(file)
      @file = file

    def toc


    def get_nodes
      page = Nokogiri::HTML("source/#{file}"))
      page.css('h2, h3')

    def get_outline
      outline = []
      last_headline = nil { |h|
        { type:, id: h['id'], title: h.text, children: [] } if !h.text.blank?
      }.compact.delete_if { |h|
        case h[:type]
        when "h2"
          outline << h
          last_headline = h
        when "h3"
          last_headline[:children] << h

    def link_tree(outline)
      return if outline.empty?

      content_tag(:ul) do
        item_content = ''
        outline.each do |link|
          item_content << content_tag(:li) do
            link_content = ''
            link_content << link_to(link[:title], "/##{link[:id]}")
            link_content << link_tree(link[:children]) if !link[:children].empty?

Middleman will automatically load and register helper modules found in the helpers folder if the file name matches the module name. (See the Middleman documentation for Custom Defined Helpers.)

TOCBuilder uses Nokogiri to find all the h2 and h3 tags in your page, then builds the navigation by creating an unordered list for all h2 tags and sub-lists for the h3 tags under each.

Need help building or maintaining a Rails app?

Jeremy is currently booked until mid-2023, but always happy to chat.

Email Jeremy