---
title: Add navigation across your pages
description: Give the styled bare host a header menu that builds itself from the content pipeline and highlights the current page.
canonical_url: https://usepennington.net/tutorials/getting-started/navigation/
sidecar_url: https://usepennington.net/tutorials/getting-started/navigation.md
content_hash: sha256:6300a62ed4f215c8cf0638f91b465bbe742bd66929383d0a7f8bd231fa2ad72f
tokens: 2455
uid: tutorials.getting-started.navigation
reading_time_minutes: 4
---

Getting Started
# Add navigation across your pages

Give the styled bare host a header menu that builds itself from the content pipeline and highlights the current page.

 
By the end of this tutorial the styled site from [Style the site with MonorailCSS](https://usepennington.net/tutorials/getting-started/styling.md) has a navigation menu in its header. The menu links every page on the site, builds itself from the `Content/` folder — so adding a markdown file adds a menu entry — and renders the current page in bold.

 
This is the last step of the getting-started arc. After it, a bare `AddPennington` host serves a complete, styled, navigable multi-page site — no template involved.

 
## Prerequisites

 
 - .NET 10 SDK installed
 - Completed [Style the site with MonorailCSS](https://usepennington.net/tutorials/getting-started/styling.md) — this tutorial extends that project's `MainLayout.razor` and `Content/` folder
 
 
The finished code for this tutorial lives in [examples/GettingStartedNavigationExample](https://github.com/usepennington/pennington/tree/main/examples/GettingStartedNavigationExample).

 
---

 
## 1. Add pages to navigate to

 
The styling site has a single home page. A menu needs somewhere to point, so let's add three more pages — two inside a `guides/` folder, one at the top level. A folder with no `index.md` becomes a section node in the tree, so `guides/` will render as a labeled **Guides** group with its two pages nested under it.

 
**Create the `guides/` folder with two pages**

 
Add `Content/guides/installation.md` and `Content/guides/deployment.md`. The `order:` front-matter key sets each page's position in its section — lower sorts first.

 
```markdown:symbol
---
title: Installation
description: A page inside the guides section.
order: 10
---
  
This page lives at `Content/guides/installation.md`. Its URL is `/guides/installation/`,
so `NavigationBuilder` places it under a **Guides** section in the menu.
  
Its `order:` of `10` sorts it ahead of the Deployment page within that section.
```

 
```markdown:symbol
---
title: Deployment
description: Another page inside the guides section.
order: 20
---
  
This page also lives under `Content/guides/`, so it joins the Installation page
under the **Guides** section. Its `order:` of `20` places it second.
  
Open the menu and notice that the entry for the page you are on renders bold —
`NavMenu.razor` reads the `IsSelected` flag `NavigationBuilder` stamps onto the
node matching the current URL.
```

 
**Create a top-level page**

 
Add `Content/about.md` directly under the content root — not in a folder — so it becomes a top-level menu entry rather than part of a section.

 
```markdown:symbol
---
title: About
description: A top-level page outside any section.
order: 30
---
  
This page lives at `Content/about.md` — directly under the content root, not in
a folder — so it appears as a top-level menu entry rather than inside a section.
  
Its `order:` of `30` is the highest on the site, so it sorts last.
```

 
> [!CHECKPOINT]
>  - `dotnet run --urls http://localhost:5000`, then visit `http://localhost:5000/guides/installation/` and `http://localhost:5000/about/`
>  - Both pages render through the styled layout — but there is still no menu, so the only way to reach them is by typing the URL

---

 
## 2. Build the navigation menu

 
`AddPennington` already registers `NavigationBuilder` — the service that turns content into a navigation tree — so the menu needs no new wiring in `Program.cs`, only a component to render it.

 
**Add the `Pennington.Navigation` namespace to `_Imports.razor`**

 
`NavMenu.razor` uses types from `Pennington.Navigation`, so add that namespace to the project's `_Imports.razor`:

 
```razor
@using Pennington.Navigation
```

 
`<NavMenu />` is referenced by its short tag name in `MainLayout.razor`, which resolves only when the layout folder's namespace (`<RootNamespace>.Components.Layout`) is in scope. The styling tutorial already added that line when it moved the shell into `MainLayout.razor`, so it is in place — an unresolved component tag is a build warning, not an error, and `<NavMenu />` would silently render as raw markup without it.

 
**Create `Components/Layout/NavMenu.razor`**

 
This component renders the menu from the content pipeline:

 
```razor:symbol
@* NavMenu.razor — the site's navigation, built from the content pipeline.
   Every IContentService exposes its pages as flat table-of-contents entries.
   NavigationBuilder sorts those entries by their `order:` front matter and
   nests them by folder into a tree of NavigationTreeItem. This component
   renders that tree and marks the entry matching the current URL. *@
  
@inject IEnumerable<IContentService> ContentServices
@inject NavigationBuilder Navigation
@inject NavigationManager NavManager
  
<nav class="mt-3 flex flex-wrap items-center gap-x-5 gap-y-1 text-sm">
    @foreach (var item in _tree)
    {
        if (item.Children.Count == 0)
        {
            <a class="@LinkClass(item)" href="@item.Route.CanonicalPath.Value">@item.Title</a>
        }
        else
        {
            @* A folder with no index page becomes a section: a label, then its pages. *@
            <span class="font-semibold text-base-400">@item.Title</span>
            foreach (var child in item.Children)
            {
                <a class="@LinkClass(child)" href="@child.Route.CanonicalPath.Value">@child.Title</a>
            }
        }
    }
</nav>
  
@code {
    private IReadOnlyList<NavigationTreeItem> _tree = [];
  
    protected override async Task OnInitializedAsync()
    {
        // Collect the table-of-contents entries from every content source.
        var entries = await ContentServices.CollectTocEntriesAsync();
  
        // Passing the current URL lets NavigationBuilder return the matching
        // node with IsSelected already set.
        var currentPath = new UrlPath(NavManager.ToBaseRelativePath(NavManager.Uri))
            .EnsureLeadingSlash();
  
        _tree = await Navigation.BuildTreeAsync(entries, currentPath);
    }
  
    // The current page renders bold; the rest are muted links.
    private static string LinkClass(NavigationTreeItem item) => item.IsSelected
        ? "font-semibold text-primary-700"
        : "text-base-600 hover:text-primary-700";
}
```

 
`CollectTocEntriesAsync` gathers a flat list — one `ContentTocItem` per page — from every content source. `BuildTreeAsync` is what gives it shape: it sorts entries by their `order:` value and nests them by folder, so `Content/guides/` becomes a **Guides** section. Passing the current URL makes the matching node come back with `IsSelected` set. For the full picture of how the tree is folded together, see [Navigation-tree construction](https://usepennington.net/explanation/routing/navigation-tree.md).

 
## 3. Wire the menu into the layout

 
Drop `<NavMenu />` into the header of `MainLayout.razor`. Because the layout wraps every routed page, the menu then appears site-wide.

 
```razor:symbol
@* Styled shell from the styling tutorial, now with a navigation menu in the
   header. <NavMenu /> builds its links from the content pipeline, so adding a
   markdown file to Content/ adds a menu entry — no edit to this file. *@
  
@inherits LayoutComponentBase
  
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="/styles.css" />
    <HeadOutlet />
</head>
<body class="bg-base-50 text-base-900 min-h-screen">
    <div class="max-w-3xl mx-auto px-6 py-10">
        <header class="mb-8 border-b border-base-200 pb-4">
            <a class="text-lg font-bold text-primary-700" href="/">My Pennington Site</a>
            <NavMenu />
        </header>
        <article class="prose">
            @Body
        </article>
        <footer class="mt-12 pt-4 border-t border-base-200 text-xs text-base-500">
            Built on a bare Pennington host.
        </footer>
    </div>
</body>
</html>
```

 
> [!CHECKPOINT]
> Run `dotnet run --urls http://localhost:5000` and open `http://localhost:5000/`.
> 
>  - The header shows a menu: **Welcome**, a **Guides** group containing **Installation** and **Deployment**, then **About**
>  - The entry for the page you are viewing renders bold — click into `/guides/deployment/` and the highlight follows
>  - Add a new markdown file under `Content/` and reload — a menu entry appears for it with no edit to `NavMenu.razor` or `MainLayout.razor`

---

 
## Summary

 
 - `NavigationBuilder` ships with `AddPennington`; `NavMenu.razor` is the only new code, and `Program.cs` did not change.
 - `NavMenu.razor` collects each source's table-of-contents entries and `NavigationBuilder.BuildTreeAsync` turns that flat list into a sorted, folder-nested tree.
 - The bare host now serves a complete site: a content pipeline, a styled layout, and navigation — all on `AddPennington`.
 
 
That is the whole getting-started arc. `AddPennington` gives you the lower-level host: you wire the pipeline, the layout, and the navigation yourself, and you have now done each part. The [DocSite](https://usepennington.net/tutorials/docsite/scaffold.md) and [BlogSite](https://usepennington.net/tutorials/blogsite/scaffold.md) templates package this wiring for documentation and blog sites. The [beyond-basics tutorials](https://usepennington.net/tutorials/beyond-basics/custom-razor-component.md) build on the host you just finished.

 
[Previous
                
                Style the site with MonorailCSS](https://usepennington.net/tutorials/getting-started/styling.md)[Next
                    
                Scaffold a documentation site with DocSite](https://usepennington.net/tutorials/docsite/scaffold.md)