Color mode

Navigation

Site navigation as a Riebeckite plugin. navigation() derives the primary navigation from the notes already in the vault, so an existing Obsidian vault becomes navigable with no Riebeckite-specific file and no required frontmatter. The plugin owns the navigation model and the rendering mechanics; the Site owns placement — where each rendered list appears.

日本語

Overview

navigation() reads the manifest's discoverable entries — the notes that are published and routable — and groups them by their slug hierarchy:

  • a folder becomes a section;
  • an index or README note links its folder, and the folder's first-level note becomes its child;
  • every other note becomes a link;
  • titles come from the note's own title, falling back to the slug segment.

A folder with no index note still renders as a label grouped around its children. Drafts, scheduled and otherwise non-discoverable notes never appear, because the model is built from manifest.discoverableEntries.

Nothing in the vault has to change: no navigation.md, no navigation frontmatter, no re-declaring what the folder structure already says.

Usage

ts
import { defineConfig } from "@riebeckite/core";
import { navigation } from "@riebeckite/plugin-navigation";
 
export default defineConfig({
  // ...
  plugins: [navigation()],
});

The Site shell renders the model. In a HonoX site:

tsx
import { content } from "../content";
import { resolveSiteNavigation } from "@riebeckite/plugin-navigation";
 
const model = resolveSiteNavigation(config, await content.getManifest());
const primary = model?.primary ?? [];
const secondary = model?.secondary ?? [];

resolveSiteNavigation returns null when the plugin is not registered, so a site without navigation keeps working.

Rendering

SiteNav renders a resolved tree as a <nav> landmark with nested lists. It owns the rendering mechanics — recursive children, active-path detection, locale-aware normalization, external-link handling, and aria-current — and ships the structural CSS for that rb-nav tree in its own style.css, loaded through the generated plugin styles. The Site decides where the tree is placed and whether to wrap it in a <details> element on small screens:

tsx
import { SiteNav } from "@riebeckite/plugin-navigation";
 
<header class="site-header rb-site-header">
  <a href="/" class="site-header__home rb-site-header__home">My Site</a>
  <SiteNav
    items={primary}
    path={c.req.path}
    language={c.get("htmlLanguage")}
  />
</header>;

path is the current request path. language is the current content language; when set, SiteNav strips a matching /language prefix before resolving the active item, so unlocalized authored hrefs still match localized pages. Pass localizeHref to rewrite hrefs for the current language, label to change the landmark label, and class/className to extend the <nav> classes.

Authored navigation

Automated derivation cannot express everything, and some sites curate their links deliberately. Pass items to replace derivation with authored links:

ts
navigation({
  items: [
    { label: "Docs", href: "/docs/" },
    { label: "Reference", href: "/docs/reference/" },
    {
      label: "Themes",
      href: "/docs/themes/",
      children: [
        { label: "Default", href: "/docs/themes/default" },
        { label: "Writing a theme", href: "/docs/themes/writing-a-theme" },
      ],
    },
  ],
});

secondary holds supplementary links a Site may render less prominently:

ts
navigation({
  secondary: [
    { label: "Docs", href: "/docs/" },
    { label: "GitHub", href: "https://github.com/Rerurate514/riebeckite", external: true },
  ],
});

primary and secondary describe prominence, not placement. Where each list is rendered — a header, a footer, a sidebar — is the Site's decision.

Options

Option Type Default Description
items NavigationItem[] derived Authored primary navigation
secondary NavigationItem[] [] Supplementary links

NavigationItem is { label, href?, children?, external? }. href is optional on the type because a derived folder group may not have an index note; authored items require a non-empty href, and external marks links that open in a new tab.

Exports

  • navigation(options?) — plugin factory
  • navigationPlugin — alias of navigation
  • NAVIGATION_PLUGIN_NAME — the plugin name, "navigation"
  • buildNavigation(entries, options?) — build a model from manifest entries
  • resolveSiteNavigation(config, manifest) — find the enabled plugin and build the model, or null
  • validateNavigationOptions(options) — option validation
  • SiteNav — render a resolved tree with the standard rb-nav structure
  • Types: NavigationItem, NavigationOptions, NavigationSource, SiteNavigation, SiteNavProps

Limitations

  • Derivation follows the rendered language when the l10n plugin writes language metadata, so a localized vault's navigation never mixes languages. In a vault without localization metadata, every discoverable entry is used.
  • Ordering is alphabetical by title. Frontmatter ordering is not read.
  • There is no exclude/include/order option yet; add one only when a real site needs it.

See also

History

1 changesCollapseExpand
1 + <!-- Generated from packages/plugins/navigation/README.md. Do not edit this page directly; edit the package README and run `pnpm docs:sync`. -->
2 +
3 + # Navigation
4 +
5 + Site navigation as a Riebeckite plugin. `navigation()` derives the primary
6 + navigation from the notes already in the vault, so an existing Obsidian vault
7 + becomes navigable with no Riebeckite-specific file and no required frontmatter.
8 + The plugin owns the navigation *model* and the *rendering mechanics*; the Site
9 + owns placement — where each rendered list appears.
10 +
11 + [日本語](./navigation.md)
12 +
13 + ## Overview
14 +
15 + `navigation()` reads the manifest's discoverable entries — the notes that are
16 + published and routable — and groups them by their slug hierarchy:
17 +
18 + - a folder becomes a section;
19 + - an `index` or `README` note links its folder, and the folder's first-level
20 + note becomes its child;
21 + - every other note becomes a link;
22 + - titles come from the note's own `title`, falling back to the slug segment.
23 +
24 + A folder with no index note still renders as a label grouped around its
25 + children. Drafts, scheduled and otherwise non-discoverable notes never appear,
26 + because the model is built from `manifest.discoverableEntries`.
27 +
28 + Nothing in the vault has to change: no `navigation.md`, no navigation
29 + frontmatter, no re-declaring what the folder structure already says.
30 +
31 + ## Usage
32 +
33 + ```ts
34 + import { defineConfig } from "@riebeckite/core";
35 + import { navigation } from "@riebeckite/plugin-navigation";
36 +
37 + export default defineConfig({
38 + // ...
39 + plugins: [navigation()],
40 + });
41 + ```
42 +
43 + The Site shell renders the model. In a HonoX site:
44 +
45 + ```tsx
46 + import { content } from "../content";
47 + import { resolveSiteNavigation } from "@riebeckite/plugin-navigation";
48 +
49 + const model = resolveSiteNavigation(config, await content.getManifest());
50 + const primary = model?.primary ?? [];
51 + const secondary = model?.secondary ?? [];
52 + ```
53 +
54 + `resolveSiteNavigation` returns `null` when the plugin is not registered, so a
55 + site without navigation keeps working.
56 +
57 + ### Rendering
58 +
59 + `SiteNav` renders a resolved tree as a `<nav>` landmark with nested lists. It
60 + owns the rendering mechanics — recursive children, active-path detection,
61 + locale-aware normalization, external-link handling, and `aria-current` — and
62 + ships the structural CSS for that `rb-nav` tree in its own `style.css`, loaded
63 + through the generated plugin styles. The Site decides where the tree is placed
64 + and whether to wrap it in a `<details>` element on small screens:
65 +
66 + ```tsx
67 + import { SiteNav } from "@riebeckite/plugin-navigation";
68 +
69 + <header class="site-header rb-site-header">
70 + <a href="/" class="site-header__home rb-site-header__home">My Site</a>
71 + <SiteNav
72 + items={primary}
73 + path={c.req.path}
74 + language={c.get("htmlLanguage")}
75 + />
76 + </header>;
77 + ```
78 +
79 + `path` is the current request path. `language` is the current content language;
80 + when set, `SiteNav` strips a matching `/language` prefix before resolving the
81 + active item, so unlocalized authored hrefs still match localized pages. Pass
82 + `localizeHref` to rewrite hrefs for the current language, `label` to change the
83 + landmark label, and `class`/`className` to extend the `<nav>` classes.
84 +
85 + ## Authored navigation
86 +
87 + Automated derivation cannot express everything, and some sites curate their
88 + links deliberately. Pass `items` to replace derivation with authored links:
89 +
90 + ```ts
91 + navigation({
92 + items: [
93 + { label: "Docs", href: "/docs/" },
94 + { label: "Reference", href: "/docs/reference/" },
95 + {
96 + label: "Themes",
97 + href: "/docs/themes/",
98 + children: [
99 + { label: "Default", href: "/docs/themes/default" },
100 + { label: "Writing a theme", href: "/docs/themes/writing-a-theme" },
101 + ],
102 + },
103 + ],
104 + });
105 + ```
106 +
107 + `secondary` holds supplementary links a Site may render less prominently:
108 +
109 + ```ts
110 + navigation({
111 + secondary: [
112 + { label: "Docs", href: "/docs/" },
113 + { label: "GitHub", href: "https://github.com/Rerurate514/riebeckite", external: true },
114 + ],
115 + });
116 + ```
117 +
118 + `primary` and `secondary` describe prominence, not placement. Where each list
119 + is rendered — a header, a footer, a sidebar — is the Site's decision.
120 +
121 + ## Options
122 +
123 + | Option | Type | Default | Description |
124 + | ------ | ---- | ------- | ----------- |
125 + | `items` | `NavigationItem[]` | derived | Authored primary navigation |
126 + | `secondary` | `NavigationItem[]` | `[]` | Supplementary links |
127 +
128 + `NavigationItem` is `{ label, href?, children?, external? }`. `href` is optional
129 + on the type because a derived folder group may not have an index note; authored
130 + items require a non-empty `href`, and `external` marks links that open in a new
131 + tab.
132 +
133 + ## Exports
134 +
135 + - `navigation(options?)` — plugin factory
136 + - `navigationPlugin` — alias of `navigation`
137 + - `NAVIGATION_PLUGIN_NAME` — the plugin name, `"navigation"`
138 + - `buildNavigation(entries, options?)` — build a model from manifest entries
139 + - `resolveSiteNavigation(config, manifest)` — find the enabled plugin and build
140 + the model, or `null`
141 + - `validateNavigationOptions(options)` — option validation
142 + - `SiteNav` — render a resolved tree with the standard `rb-nav` structure
143 + - Types: `NavigationItem`, `NavigationOptions`, `NavigationSource`,
144 + `SiteNavigation`, `SiteNavProps`
145 +
146 + ## Limitations
147 +
148 + - Derivation follows the rendered language when the `l10n` plugin writes
149 + language metadata, so a localized vault's navigation never mixes languages.
150 + In a vault without localization metadata, every discoverable entry is used.
151 + - Ordering is alphabetical by title. Frontmatter ordering is not read.
152 + - There is no `exclude`/`include`/`order` option yet; add one only when a real
153 + site needs it.
154 +
155 + ## See also
156 +
157 + - [Configuration reference](../reference/configuration.en.md)
158 + - [Plugin guide](../reference/plugin-api.en.md)
159 +