---
title: "Build navigation"
description: "Turn your folders into a nested navigation tree with content.navigation()."
canonical_url: "https://content.comark.dev/guide/navigation"
---
# Build navigation

> Turn your folders into a nested navigation tree with content.navigation().

A sidebar, a table of contents, or a footer site map all need the same thing: your pages arranged as a tree. [`content.navigation()`](https://content.comark.dev/reference/content/navigation) builds that tree from your folders, so you don't maintain a separate list of links.

## From directories to a tree

Each directory becomes a group, each Markdown file becomes an item inside it. Given this source:

```
content/
  index.md
  1.getting-started/
    1.installation.md
    2.configuration.md
  2.concepts/
    .navigation.yml
    index.md
    1.how-it-works.md
```

calling `await content.navigation()` returns:

```ts
[
  {
    title: 'Getting Started',      // generated from the directory name
    path: '/getting-started',
    page: false,                   // no index.md, unlinked group
    children: [
      { title: 'Installation', path: '/getting-started/installation' },
      { title: 'Configuration', path: '/getting-started/configuration' },
    ],
  },
  {
    title: 'Core concepts',        // from .navigation.yml
    path: '/concepts',             // links to its index.md
    children: [
      { title: 'Concepts', path: '/concepts' },
      { title: 'How it works', path: '/concepts/how-it-works' },
    ],
  },
  { title: 'Home', path: '/' },    // `index` sorts after the numbered folders
]
```

Items are sorted by file name, so the root `index.md` lands after `1.getting-started/` and `2.concepts/`. Rename it `0.index.md` to put it first; the prefix is stripped from its path either way.

Each entry is a [`NavigationItem`](https://content.comark.dev/reference/types/content#navigationitem):

```ts
interface NavigationItem {
  title: string            // from frontmatter (or generated for directories)
  description?: string     // frontmatter description
  path: string             // public URL path, e.g. '/getting-started/installation'
  children?: NavigationItem[]
  page?: false             // directory group with no linkable page
  [key: string]: unknown   // extra fields from `navigation` overrides
}
```

## Render the tree

The tree is plain data, so a recursive component renders it. In Vue, for example:

```vue [Sidebar.vue]
<script setup lang="ts">
import type { NavigationItem } from 'comark-content'

defineProps<{ items: NavigationItem[] }>()
</script>

<template>
  <ul>
    <li v-for="item in items" :key="item.path">
      <a v-if="item.page !== false" :href="item.path">{{ item.title }}</a>
      <span v-else>{{ item.title }}</span>
      <Sidebar v-if="item.children" :items="item.children" />
    </li>
  </ul>
</template>
```

Groups with `page: false` have no page of their own, so render them as headings rather than links. Everything below explains how to control what ends up in the tree.

## Ordering with numeric prefixes

Items are sorted by their file and directory names with **natural numeric collation**, so you order pages by prefixing them with `<number>.`:

```
1.getting-started/   → first
2.concepts/          → second
10.advanced/         → tenth (not between 1 and 2)
```

Prefixes affect **ordering only** — they are stripped from public paths, so `1.getting-started/2.configuration.md` is served at `/getting-started/configuration`. Files without a prefix sort alphabetically. Version-like segments such as `1.2.x` are preserved as-is in the URL.

## Titles

A navigation item's title is resolved in order:

1. `navigation.title` in the page's frontmatter.
2. `title` in the page's frontmatter.
3. For directories without a configured title, a title generated from the directory name: `getting-started` → `Getting Started`.

## Index files

An `index.md` (or `3.index.md` — the prefix still orders it) represents the directory itself:

- The root `index.md` becomes the `/` entry.
- A directory's `index.md` is listed inside that directory's `children`, and the directory node links to it.
- A directory **without** an index page still appears as a group, marked with `page: false`, so you can render it as an unlinked heading.

## Customizing with `navigation` frontmatter

The `navigation` key in a page's frontmatter controls how it appears in the tree:

```md [1.installation.md]
---
title: Installation
navigation:
  title: Install       # shown in the nav instead of `title`
  icon: i-lucide-download
---
```

Every key inside `navigation` is copied onto the item, so custom fields like `icon` or `badge` are available when rendering.

Set it to `false` to hide a page from navigation entirely:

```md [drafts.md]
---
navigation: false
---
```

The page is only removed from the tree — it still resolves through [`get()`](https://content.comark.dev/reference/content/get) and [`list()`](https://content.comark.dev/reference/content/list). If a directory's index page opts out, the directory survives as an unlinked group (`page: false`); if the directory itself opts out (see below), the whole subtree is dropped and no empty ancestors are left behind.

## Directory metadata with `.navigation.yml`

A directory can carry its own navigation metadata in a `.navigation.yml` file. YAML parsing is opt-in, so register the [`yaml` plugin](https://content.comark.dev/plugins/built-in/yaml) on your Content instance first:

```ts [content.ts] {2,7}
import { comarkContent } from 'comark-content'
import yaml from 'comark-content/plugins/yaml'
import fs from 'comark-content/sources/fs'

const content = comarkContent({
  source: fs('./content'),
  plugins: [yaml()],
})
```

The file's top-level fields are merged onto the directory node:

```yaml [2.concepts/.navigation.yml]
title: Core concepts
icon: i-lucide-lightbulb
```

`navigation: false` is the special top-level field for hiding an entire directory from the tree:

```yaml [drafts/.navigation.yml]
navigation: false
```

::note
The navigation tree is derived from your files, so it's versioned with your content: reading at a different commit through a [GitHub source](https://content.comark.dev/sources/github) ref yields that commit's navigation.
::


## Sitemap

See the full [sitemap](https://content.comark.dev/sitemap.md) for all pages.
