Skip to main content

Configuration

Navigation

Organize your docs with flat groups or tabbed sections in nookdocs.config.json.

Two navigation modes

NookDocs supports two ways of organizing your sidebar. Pick whichever matches the shape of your docs — the platform figures out the rest.

Flat groups

A single sidebar with top-level groups. Best for small-to-medium docs sites with one audience.

Tabbed sections

Top-navbar tabs switch the sidebar between independent sections. Best when you have distinct audiences (e.g. Guides vs. API Reference).

Flat groups

The simplest configuration. One sidebar, multiple groups, each group holds a list of pages.

{
  "navigation": [
    {
      "group": "Getting Started",
      "pages": [
        "introduction",
        "quickstart"
      ]
    },
    {
      "group": "Configuration",
      "pages": [
        "configuration/config-file",
        "configuration/custom-domain"
      ]
    }
  ]
}

The sidebar renders every group in order. Breadcrumbs show the parent group as an eyebrow label above the h1. Prev/next walks the full flat sequence.

Where the labels come from

You list paths here, not names — each page's label comes from its own frontmatter title. When a title is too long for the sidebar (roughly 23 characters at the default width), set sidebarTitle on that page to give the rail a shorter form while the page keeps its full heading:

---
title: Data Processing Agreement (DPA)
sidebarTitle: DPA
---

A label that still doesn't fit is truncated with an ellipsis, and hovering it shows the full text. See frontmatter for the rest of the per-page fields.

Tabbed sections

When you have distinct top-level sections that deserve their own sidebar, wrap each one in a tab:

Where the tabs are drawn is the theme's call, not something you configure per site. Most themes run them as a strip across the header; larch lists them down the rail, and slate — which has no header at all — collapses them into a single dropdown at the top of the rail. The same navigation array produces all three. See the theme catalog.

{
  "navigation": [
    {
      "tab": "Documentation",
      "groups": [
        {
          "group": "Getting Started",
          "pages": ["introduction", "quickstart"]
        },
        {
          "group": "Configuration",
          "pages": ["configuration/config-file"]
        }
      ]
    },
    {
      "tab": "API Reference",
      "groups": [
        {
          "group": "Endpoints",
          "pages": [
            "api/authentication",
            "api/projects",
            "api/deployments"
          ]
        }
      ]
    }
  ]
}

The header renders Documentation and API Reference as tabs in the top navbar. Clicking a tab switches the sidebar to show only that tab's groups. The active tab is detected from the current URL — whichever tab contains the page you are reading gets the primary-color underline.

Tabbed mode is all-or-nothing at the top level. Either every top-level entry is a tab, or every top-level entry is a group. Mixing the two is not supported and will cause only the tabs to render.

On phones and small tablets the tabs move into the navigation drawer, where they collapse into a single control showing the tab you are currently reading. Tapping it lists every tab. This keeps all of them reachable no matter how many you define — a horizontal row would push the later ones off-screen. The control is hidden entirely when you only have one tab.

The header itself also changes shape at that size: the logo keeps its own row, and below it a second row carries the menu button, a short trail showing the group and page you are reading, and the appearance toggle. Nothing here is configurable — it follows from your navigation structure.

Prev/next within tabs

In tabbed mode, prev/next stay inside the current tab. The Next button on the last Documentation page does not jump to the first API Reference page — each tab is a self-contained reading sequence.

In flat mode, prev/next walks the entire navigation as one list.

File path convention

Entries in pages are file paths relative to your docs base directory, without the .mdx extension. The platform converts them to URL paths automatically:

EntryFileURL
introductionintroduction.mdx/introduction
api/authapi/auth.mdx/api/auth
guides/deployguides/deploy.mdx/guides/deploy

In the default flat layout, page refs map straight to the file path at the base. A docs/ prefix is only used if you set contentRoot: "docs/" to nest your content in a subfolder.

Nested groups

Put a group object inside another group's pages array to create a sub-section. The children render in declaration order, so a sub-group placed between two pages keeps the page after it below it in the sidebar:

{
  "group": "Deploy",
  "pages": [
    "deploy/subdomain",
    "deploy/custom-domain",
    {
      "group": "Host at a subpath",
      "icon": "route",
      "collapsible": true,
      "root": "deploy/subpath",
      "pages": [
        "deploy/cloudflare",
        "deploy/vercel",
        "deploy/aws-cloudfront"
      ]
    },
    "deploy/monorepo"
  ]
}

This renders:

Deploy
├─ Subdomain
├─ Custom domain
├─ Host at a subpath   ← sub-group (indented, with a left rail)
│   ├─ Cloudflare
│   ├─ Vercel
│   └─ AWS Route 53 + CloudFront
└─ Monorepo            ← stays after the sub-group

A sub-group is just a group object, so it accepts every group field — icon, tag, root, collapsible, and even further-nested pages. Children stay in the flattened order, so prev/next and breadcrumbs flow through the sub-group's pages.

Give the sub-group a root (see Group root pages) so clicking its label opens an overview page — combined with interaction.drilldown: true (the default), the label becomes a real link instead of just a toggle.

Collapsible groups

Set collapsible: true on a group to give it a chevron toggle even when your theme keeps groups expanded — handy for a long sub-section you want collapsed by default. It overrides the theme's collapsibleGroups behavior for that one group; omit it to inherit the theme default. (Set expanded: true alongside it to start open but still allow collapsing.)

{
  "group": "Host at a subpath",
  "collapsible": true,
  "root": "deploy/subpath",
  "pages": ["deploy/cloudflare", "deploy/vercel"]
}

Full example

A real multi-audience docs config — the NookDocs dogfood setup uses this exact pattern:

{
  "name": "NookDocs",
  "navigation": [
    {
      "tab": "Documentation",
      "groups": [
        {
          "group": "Getting Started",
          "pages": ["introduction", "quickstart", "concepts"]
        },
        {
          "group": "Features",
          "pages": [
            "features/search",
            "features/theme-toggle",
            "features/reading-experience",
            "features/code-blocks",
            "features/copy-for-llms"
          ]
        },
        {
          "group": "Configuration",
          "pages": [
            "configuration/config-file",
            "configuration/custom-domain",
            "configuration/navigation",
            "configuration/theme"
          ]
        }
      ]
    },
    {
      "tab": "Components",
      "groups": [
        {
          "group": "Basics",
          "pages": [
            "components/callout",
            "components/card",
            "components/code-group",
            "components/tabs",
            "components/accordion"
          ]
        },
        {
          "group": "Layout",
          "pages": ["components/steps", "components/frame"]
        },
        {
          "group": "API docs",
          "pages": [
            "components/param-field",
            "components/response-field"
          ]
        }
      ]
    },
    {
      "tab": "API Reference",
      "groups": [
        {
          "group": "Endpoints",
          "pages": [
            "api/authentication",
            "api/projects",
            "api/deployments"
          ]
        }
      ]
    }
  ]
}

Per-entry extras

Groups and tabs accept optional fields that the renderer surfaces in the sidebar. All of them are optional — a config that only uses group + pages keeps rendering the same way it did before.

Badges with tag

Drop a short string next to a group or tab label. Good for highlighting new sections or deprecations.

{
  "navigation": [
    {
      "group": "Payments",
      "tag": "BETA",
      "pages": ["payments/overview", "payments/webhooks"]
    }
  ]
}

Tags longer than 8 characters truncate with an ellipsis.

That is the route for a group or tab badge — it lives in the config, because a group is a config entry and has no file of its own.

A page can carry its own badge in frontmatter instead, the same way it carries its title and icon:

---
title: Batch
icon: boxes-stacked
tag: Beta
---

Both routes render the same pill. Where a page declares one and the navigation entry sets another, the navigation entry wins — it is the more specific statement, the same precedence label has over the page's title.

Hiding entries

Set hidden: true on a group, tab, or page to drop it from the sidebar. The page URLs still work — handy for draft pages or links you want to give out but not advertise.

{
  "group": "Internal",
  "hidden": true,
  "pages": ["internal/metrics"]
}

Icons on tabs

Tabs accept an icon (Lucide name — see Icons).

{
  "tab": "API Reference",
  "icon": "code",
  "groups": [...]
}

External tabs

Give a tab an href to turn it into an external link that opens in a new tab. Skip groups and pages — the tab becomes a pure link.

{
  "tab": "Changelog",
  "icon": "rss",
  "href": "https://github.com/your-org/your-repo/releases"
}

Group root pages

Give a group a dedicated landing page by setting root to a page path. Clicking the group label routes to that page instead of just toggling the section.

{
  "group": "Guides",
  "root": "guides/index",
  "pages": [
    "guides/authentication",
    "guides/webhooks",
    "guides/rate-limits"
  ]
}

Auto directory listings

When a group has a root, opt into rendering its children on that root page with directory:

accordion

A collapsible list of children — each row expands to show the page's description.

card

A responsive grid of cards, one per child, with title + description.

{
  "group": "Guides",
  "root": "guides/index",
  "directory": "card",
  "pages": [
    "guides/authentication",
    "guides/webhooks"
  ]
}

Child page descriptions (used by both variants) come from the MDX frontmatter's description field. Keep them to a single line — two at most.

Toggle the behavior of clicks on a group with a root via the top-level interaction.drilldown flag. Default: true (clicks route to the root). Set to false and clicks only open/close the group:

{
  "interaction": { "drilldown": false }
}

Per-page overrides

Replace a bare string in pages with an object to customize how that one entry appears — without touching the page's own frontmatter.

{
  "group": "Guides",
  "pages": [
    "guides/authentication",
    {
      "page": "guides/webhooks",
      "label": "Webhooks (advanced)",
      "icon": "zap",
      "tag": "NEW"
    },
    {
      "href": "https://github.com/your-org/your-repo",
      "label": "Source on GitHub",
      "icon": "github"
    }
  ]
}

Two object forms are supported:

  • { "page": "..." } — an internal docs page with optional label, icon, tag, hidden overrides.

  • { "href": "...", "label": "..." } — an external link that renders as a new-tab sidebar item.

Explicit expand state

By default, groups expand when they contain the current page. Override with expanded:

{
  "group": "Reference",
  "expanded": false,
  "pages": ["reference/api", "reference/cli"]
}
  • expanded: true — always open.

  • expanded: false — collapsed on first visit. The user can still toggle manually.

  • Omitted — auto (the old behavior).

Anchors and dropdowns (Phase 3)

Anchors and dropdowns sit at the top of the sidebar, above the groups. They're how you surface high-level destinations (community, changelog, status page) or organize content into collapsible top-level buckets.

Anchors

An anchor is a standalone sidebar item. Two shapes, same discriminator (anchor):

External link

{
  "navigation": [
    {
      "anchor": "Community",
      "icon": "users",
      "href": "https://github.com/your-org/your-repo/discussions",
      "tag": "Discord"
    },
    { "group": "Getting Started", "pages": ["introduction"] }
  ]
}

Renders above the "Getting Started" group with a little external-link arrow and opens in a new tab.

Inline expandable

Drop the href and add pages / groups and the anchor becomes a collapsible section — like a group, but it lives above the main group list.

{
  "anchor": "Releases",
  "icon": "rocket",
  "expanded": false,
  "pages": [
    "releases/v2-0-0",
    "releases/v1-9-0",
    "releases/v1-8-0"
  ]
}

expanded: true starts the anchor open; omit it and it's closed on first visit.

Dropdowns

Dropdowns are always collapsible with children — no plain-link mode. Use them when you have multiple top-level product areas that don't warrant a full tab but need their own mini-navigation.

{
  "dropdown": "Reference",
  "icon": "book",
  "description": "API + CLI reference",
  "expanded": true,
  "groups": [
    {
      "group": "API",
      "pages": ["api/authentication", "api/projects"]
    },
    {
      "group": "CLI",
      "pages": ["cli/overview", "cli/commands"]
    }
  ]
}

Dropdowns always carry a toggle. expanded: true starts one open; leave it out and it starts closed. Either way the reader's own choice is remembered from then on, so this only sets what they see first.

A dropdown can also hold a flat list of links rather than groups. Each entry takes its own icon, the same way a page does in the sidebar:

{
  "dropdown": "Resources",
  "icon": "compass",
  "pages": [
    { "label": "GitHub", "href": "https://github.com/your-org", "icon": "tabler:brand-github" },
    { "label": "Quick Start", "href": "/quickstart", "icon": "rocket" }
  ]
}

An href starting with / is treated as one of your own pages: it opens in the same tab, with no outbound arrow, exactly as it would in the sidebar proper. Anything else opens in a new tab. Add "target": "_blank" to force a new tab for an internal link.

Icons are picked from a searchable list in the dashboard under Configurations → Navigation → Global nav, so you don't have to know the name.

When to use which

Anchor (external)Anchor (inline)DropdownGroup
Has childrenNoYes (optional)Yes (required)Yes (required)
Can be a plain linkYesNoNoNo
PositionTop of sidebarTop of sidebarTop of sidebarMain list
CollapsibleN/AYesYesYes
Open state persistsN/AToggleableYes (localStorage)Yes (in-memory)

Rule of thumb:

  • Linking out of the docs → anchor with href.

  • Small side section (status, releases, changelog) → anchor with pages.

  • Major parallel section that's a peer of your main content → dropdown.

  • Primary content tree → groups (default).

Hidden state

All three support hidden: true. The entry vanishes from the sidebar; its URLs still resolve.

Icons

All three support icon (Lucide icon name). The icon shows to the left of the label, styled to match the sidebar typography.

Multi-dimensional navigation (Phase 3+)

Bigger docs sites need switchers at the top of the page:

  • Versionsv1 / v2 / v3 of your API

  • Languagesen / tr / fr locales

  • Products — multi-product companies with separate docs per product

All three render as dropdowns in the navbar; the user picks, the URL changes, and the sidebar updates to match. The platform uses the URL's first path segment as the active selector.

Versions

{
  "versions": [
    { "version": "v3", "default": true, "tag": "Latest", "status": "stable" },
    { "version": "v2", "status": "stable" },
    { "version": "v1", "tag": "Deprecated", "status": "deprecated" }
  ]
}

Each entry's URL slug is the version label lowercased (override with slug). The default: true entry serves at the root (/), others serve at /<slug>/....

status colors the tag: "stable" → primary, "beta" → warning, "deprecated" → danger, "preview" → info.

Languages

{
  "languages": [
    { "language": "en", "default": true, "label": "English" },
    { "language": "tr", "label": "Türkçe" }
  ]
}

Same slug-matching logic as versions. label is what the switcher shows — leave it out and readers see the raw code (en, tr) instead of a name they recognise.

Right-to-left layout is on the roadmap. Pages in an RTL language serve correctly today; the reading direction doesn't flip yet.

Products

{
  "products": [
    { "product": "Cloud", "default": true, "description": "Managed hosting" },
    { "product": "Enterprise", "description": "Self-hosted" }
  ]
}

Products render a switcher near the logo (when shipped); path prefix /<slug>/... routes to the product's own nav tree.

Content layout

For versions / languages / products, the platform expects tenants to organise their MDX under matching path prefixes in the repo. Example for a v3 / v2 / v1 setup:

your-repo/                    # the docs base
├── introduction.mdx          # default (v3)
├── guides/
│   └── authentication.mdx    # default (v3)
├── v2/
│   ├── introduction.mdx
│   └── guides/
│       └── authentication.mdx
└── v1/
    ├── introduction.mdx
    └── guides/
        └── authentication.mdx

Per-tab anchors + dropdowns

Tabs can carry their own anchors and dropdowns — they render above the tab's group list when that tab is active:

{
  "tab": "Guides",
  "icon": "book",
  "anchors": [
    { "anchor": "Status page", "icon": "activity", "href": "https://status.example.com" }
  ],
  "groups": [
    { "group": "Getting Started", "pages": ["guides/intro"] }
  ]
}

Root-level anchors still render when no tab is active (flat navigation mode). In tabbed mode the sidebar uses the current tab's own anchors only.

Global nav

Use global for chrome that must survive product / version / language switches — a status-page anchor that's visible everywhere, a changelog dropdown that's the same across versions, etc.

{
  "global": {
    "anchors": [
      { "anchor": "Status", "icon": "activity", "href": "https://status.example.com" }
    ],
    "dropdowns": [
      {
        "dropdown": "Changelog",
        "icon": "history",
        "pages": ["changelog/2026-q1", "changelog/2025-q4"]
      }
    ]
  }
}

Global anchors always render first in the sidebar; scope-specific anchors follow below.

Tab menus (rich dropdowns)

A tab with menus becomes a click-to-open dropdown showing rich items (title + description + icon) — think Mintlify's "mega menu" pattern.

{
  "tab": "Products",
  "icon": "grid-2x2",
  "menus": [
    {
      "item": "Analytics",
      "description": "Real-time dashboards and event tracking.",
      "icon": "bar-chart",
      "pages": ["analytics/overview"]
    },
    {
      "item": "Webhooks",
      "description": "Stream events to your infrastructure.",
      "icon": "zap",
      "tag": "BETA",
      "pages": ["webhooks/overview"]
    },
    {
      "item": "Changelog",
      "description": "What shipped this quarter.",
      "icon": "history",
      "href": "https://example.com/changelog"
    }
  ]
}

Click a menu item → navigates to its href or first child page. The sidebar then reflects the destination's navigation, same as clicking a regular tab.

Each menu item supports: item (title, required), description, icon, iconType, tag, hidden, href, pages, groups.

Was this page helpful?

Last updated August 20, 2026