Color mode

Customizing Your Site

A site generated by Riebeckite is a normal HonoX application. You do not need to learn a Riebeckite-specific UI framework; pages, components, CSS, and interactive UI all change the same way as in plain HonoX / Hono JSX.

Riebeckite mainly handles:

  • Reading content such as Markdown
  • Passing content and plugin information to the site
  • Connecting pages and UI that plugins provide to the site

The app/ directory that actually shapes the site is site-owned code.

Where to make changes

Start by finding the location that matches what you want to change.

What you want to change Main location
Pages and URLs app/routes/
404 (page not found) app/routes/_404.tsx
Header / Footer app/components/ and app/routes/_renderer.tsx
Article page structure app/components/article.tsx
Buttons and other UI app/components/
Interactive UI app/islands/
Color, spacing, type, and layout app/style.css and local CSS
Header / Footer links the navigation plugin in riebeckite.config.ts

As a rule, editing files under app/ changes how the site looks and is structured. The exception is app/.riebeckite/, which Riebeckite generates automatically; it is refreshed on every build, so do not edit it directly.

Creating components

You can write ordinary Hono JSX components under app/components/.

tsx
// app/components/callout.tsx
export function Callout({ children }: { children?: unknown }) {
  return <aside class="callout">{children}</aside>;
}

Import the component from a route or another component as usual.

Riebeckite UI primitives

When building article pages you can also use the UI primitives that @riebeckite/honox/ui provides:

  • Article
  • ArticleLayout
  • ArticleContent

They are small pieces for assembling Riebeckite's article structure. They are not a dedicated component framework, so you do not have to use them; a site can build its own HTML structure instead.

See UI primitives.

Changing the article page layout

In the starter, the article page is mainly assembled in two places:

text
app/components/article.tsx
app/routes/_renderer.tsx

app/components/article.tsx

SiteArticle here decides the article page layout.

Typical changes made here include:

  • Moving the article title
  • Adding UI before or after the article
  • Moving breadcrumbs
  • Moving backlinks or related posts
  • Adding a sidebar

SiteArticle is built on the Article primitive from @riebeckite/honox/ui, but it is a site component, so you can edit it freely.

Note that the Article used in a route example is this site component, not the @riebeckite/honox/ui Article with the same name.

app/routes/_renderer.tsx

_renderer.tsx is the outer layout that wraps the whole site. It mainly manages:

  • <head>
  • Header
  • Footer
  • Navigation
  • Shared UI for the whole page
  • Head tags and styles generated by Riebeckite or plugins
  • RiebeckiteHead for rendering standard head contents

Edit it when you want to change something shared across the site.

To delegate the standard head contents to the framework while adding custom head elements:

tsx
import { RiebeckiteHead, ThemeRoot } from "@riebeckite/honox/ui";
 
export default jsxRenderer(({ children }, c) => (
  <ThemeRoot
    theme={config.theme}
    lang={c.get("htmlLanguage") ?? config.site.locale}
  >
    <head>
      <RiebeckiteHead
        title={config.site.title}
        headTags={c.get("headTags") ?? []}
      />
      <meta name="custom-site-value" content="..." />
    </head>
    <body class="riebeckite-page rb-site">{children}</body>
  </ThemeRoot>
);

See Head tag handoff for the RiebeckiteHead pattern and how the site keeps ownership of <head> while delegating standard head rendering to the framework.

The links shown in the Header and Footer come from the @riebeckite/plugin-navigation plugin, which you register in riebeckite.config.ts.

ts
plugins: [
  navigation({
    items: [
      { label: "Guide", href: "/guide" },
      {
        label: "Notes",
        href: "/notes/planning",
        children: [
          { label: "Planning", href: "/notes/planning" },
          { label: "Writing", href: "/notes/writing" },
        ],
      },
    ],
    secondary: [{ label: "GitHub", href: "https://github.com/example/site" }],
  }),
]

Called with no arguments, navigation() derives the links from the vault. Pass items to curate them yourself and secondary for supplementary links.

The plugin produces the model and renders each tree through SiteNav; the site decides where the rendered list is placed. In the starter, app/components/site-header.tsx uses SiteNav, and app/routes/_renderer.tsx passes the resolved model and the current path in. So it helps to think in terms of:

  • Adding or removing a link → change navigation({ items })
  • Deriving links from the vault → call navigation() with no arguments
  • Changing the Header look or HTML → change site-header.tsx
  • Changing where the Header / Footer sits → change _renderer.tsx

Rendering mechanics — recursive children, active-path detection, locale-aware normalization, external links, and aria-current — live in SiteNav, so the site never has to reimplement them.

See the configuration reference for the fields you can set.

Besides the Header / Footer navigation, plugins can add pages and links. These fall into two broad kinds.

Pages you browse (Content Discovery)

For example:

  • Search
  • Tag / Folder indexes
  • Taxonomy pages
  • Feeds
  • Sitemap

Plugins generate the pages or endpoints these need. Enabling a plugin does not by itself add a link to the Header or Footer. To show one there, add the page to navigation like any other page.

For example:

  • Breadcrumbs
  • Backlinks
  • Related Posts
  • Series previous/next links
  • Local Graph

These are mostly rendered inside article pages. A UI fragment a plugin provides can be placed through a body slot such as article.header or article.footer.

In short:

text
Header / Footer links
        ↓
navigation in riebeckite.config.ts
 
Pages such as search, tags, and folders
        ↓
plugin page types
 
Breadcrumbs, backlinks, and so on
        ↓
components / body slots

Adding routes

app/routes/ is a normal HonoX route directory, so you can add site-specific pages as usual. To create /about, for example, add it as a HonoX route.

For Markdown content and pages a plugin provides, you generally do not add a route yourself. The starter's catch-all route uses contentRouteSsgParams, riebeniteSsgParams, and resolveRiebeckiteContentRequest to resolve content and plugin page types automatically.

To show a plugin page in the Header or Footer, add a link to navigation rather than creating a new route.

404 (page not found)

Unknown URLs are handled by HonoX's standard _404.tsx route. It is an ordinary site file, so editing it changes the "page not found" screen:

tsx
// app/routes/_404.tsx
import type { NotFoundHandler } from "hono";
 
const handler: NotFoundHandler = (c) => {
  c.status(404);
 
  return c.render(
    <main class="not-found">
      <h1>Page not found</h1>
      <p>The page you requested does not exist or is not available.</p>
      <a href="/">Back to home</a>
    </main>,
  );
};
 
export default handler;

Riebeckite decides that a request is not found, but the response is rendered through _renderer.tsx, so the 404 screen reuses the site's theme, head, Header, and Footer. Two rules matter:

  • Always keep the status at 404. The generated presets call c.status(404); a pretty screen served as 200 would be wrong.
  • Presentation is site-owned. The markup, copy, links, and CSS are all yours. Riebeckite does not ship a default 404 component to override.

The 404 screen only ever sees requests that are not found. Draft, future-dated, and otherwise unpublished content never reaches it, so a 404 cannot reveal that private content exists.

Runtime errors (optional)

Hono's default error handling already logs the error and returns a plain 500 Internal Server Error, so a site does not have to add anything. If you want a site-owned visitor-facing error screen, HonoX supports app/routes/_error.tsx (an ErrorHandler). The generated presets do not add it: configuration, plugin, and build errors are developer-facing and should stay visible instead of being disguised as a page.

Using plugin components

Some plugins provide Hono JSX components you can use directly from the site. For example, you can place Backlinks or a Table of Contents anywhere you like:

tsx
import { Backlinks } from "@riebeckite/plugin-backlinks";
import { TableOfContents } from "@riebeckite/plugin-toc";
 
export function ArticleAside({ items, backlinks }: Props) {
  return (
    <aside>
      <TableOfContents items={items} />
      <Backlinks backlinks={backlinks} />
    </aside>
  );
}

Check each plugin page or package README for the available components and props. Many components can also be imported as the default export from ./components:

tsx
import Backlinks from "@riebeckite/plugin-backlinks/components";

color-mode is the exception: it exports ColorModeScript and ColorModeToggle from the package root.

Plugins that use body slots

Some plugins do not place a component directly; instead they add HTML to fixed locations on the article page. This is the body slot mechanism. The fragment appears automatically once the plugin is enabled and the site's SiteArticle renders the matching slot.

See Body slots and Providing UI or output.

Building interactive UI

Create UI that needs browser behavior, such as clicks or state, as a normal HonoX island under app/islands/. Import the island from a route or component as usual. There is no Riebeckite-specific island mechanism.

app/client.ts initializes both:

ts
createClient();
initRiebeckiteClient();

createClient() initializes the site's client behavior, and initRiebeckiteClient() starts the browser-side behavior that plugins and themes provide. Site islands live under app/islands/, while plugin browser behavior lives in the plugin, keeping the responsibilities separate.

Changing CSS

You can change the site design from app/style.css and each component's CSS. Plain CSS and Tailwind both work.

Import the CSS Riebeckite generates from plugins and themes once from the site CSS:

css
/* app/style.css */
@import "./.riebeckite/framework-styles.css";
@import "./.riebeckite/plugin-styles.css";
@import "./.riebeckite/theme-styles.css";

Do not edit files under app/.riebeckite/; they are generated. To override plugin appearance use the rr-<feature> root hook, and to adjust the Riebeckite UI primitives use the rb-* structural hooks. See CSS hooks.

Rules of thumb

If you are unsure where to make a change, this usually helps:

What you want to do Where to change it
Add a link to the Header riebeckite.config.ts
Change the Header appearance app/components/site-header.tsx
Change the site-wide shell app/routes/_renderer.tsx
Change the article page structure app/components/article.tsx
Add a custom page app/routes/
Change the 404 screen app/routes/_404.tsx
Create a custom component app/components/
Build interactive UI app/islands/
Change color or spacing app/style.css
Place plugin UI plugin component / body slot

The basic idea is that Riebeckite connects content and plugins to the site, and the site decides the final look and structure of each page.

Where to look next

History

1 changesCollapseExpand
1 + # Customizing Your Site
2 +
3 + A site generated by Riebeckite is a normal **HonoX application**. You do not
4 + need to learn a Riebeckite-specific UI framework; pages, components, CSS, and
5 + interactive UI all change the same way as in plain HonoX / Hono JSX.
6 +
7 + Riebeckite mainly handles:
8 +
9 + - Reading content such as Markdown
10 + - Passing content and plugin information to the site
11 + - Connecting pages and UI that plugins provide to the site
12 +
13 + The `app/` directory that actually shapes the site is site-owned code.
14 +
15 + ## Where to make changes
16 +
17 + Start by finding the location that matches what you want to change.
18 +
19 + | What you want to change | Main location |
20 + | --- | --- |
21 + | Pages and URLs | `app/routes/` |
22 + | 404 (page not found) | `app/routes/_404.tsx` |
23 + | Header / Footer | `app/components/` and `app/routes/_renderer.tsx` |
24 + | Article page structure | `app/components/article.tsx` |
25 + | Buttons and other UI | `app/components/` |
26 + | Interactive UI | `app/islands/` |
27 + | Color, spacing, type, and layout | `app/style.css` and local CSS |
28 + | Header / Footer links | the `navigation` plugin in `riebeckite.config.ts` |
29 +
30 + As a rule, **editing files under `app/` changes how the site looks and is
31 + structured**. The exception is `app/.riebeckite/`, which Riebeckite generates
32 + automatically; it is refreshed on every build, so do not edit it directly.
33 +
34 + ## Creating components
35 +
36 + You can write ordinary Hono JSX components under `app/components/`.
37 +
38 + ```tsx
39 + // app/components/callout.tsx
40 + export function Callout({ children }: { children?: unknown }) {
41 + return <aside class="callout">{children}</aside>;
42 + }
43 + ```
44 +
45 + Import the component from a route or another component as usual.
46 +
47 + ### Riebeckite UI primitives
48 +
49 + When building article pages you can also use the UI primitives that
50 + `@riebeckite/honox/ui` provides:
51 +
52 + - `Article`
53 + - `ArticleLayout`
54 + - `ArticleContent`
55 +
56 + They are small pieces for assembling Riebeckite's article structure. They are
57 + not a dedicated component framework, so you do not have to use them; a site can
58 + build its own HTML structure instead.
59 +
60 + See [UI primitives](../framework/honox-integration.en.md#ui-primitives).
61 +
62 + ## Changing the article page layout
63 +
64 + In the starter, the article page is mainly assembled in two places:
65 +
66 + ```text
67 + app/components/article.tsx
68 + app/routes/_renderer.tsx
69 + ```
70 +
71 + ### `app/components/article.tsx`
72 +
73 + `SiteArticle` here decides the article page layout.
74 +
75 + Typical changes made here include:
76 +
77 + - Moving the article title
78 + - Adding UI before or after the article
79 + - Moving breadcrumbs
80 + - Moving backlinks or related posts
81 + - Adding a sidebar
82 +
83 + `SiteArticle` is built on the `Article` primitive from `@riebeckite/honox/ui`,
84 + but it is a site component, so you can edit it freely.
85 +
86 + Note that the `Article` used in a route example is this site component, not the
87 + `@riebeckite/honox/ui` `Article` with the same name.
88 +
89 + ### `app/routes/_renderer.tsx`
90 +
91 + `_renderer.tsx` is the outer layout that wraps the whole site. It mainly
92 + manages:
93 +
94 + - `<head>`
95 + - Header
96 + - Footer
97 + - Navigation
98 + - Shared UI for the whole page
99 + - Head tags and styles generated by Riebeckite or plugins
100 + - **`RiebeckiteHead` for rendering standard head contents**
101 +
102 + Edit it when you want to change something shared across the site.
103 +
104 + To delegate the standard head contents to the framework while adding custom head elements:
105 +
106 + ```tsx
107 + import { RiebeckiteHead, ThemeRoot } from "@riebeckite/honox/ui";
108 +
109 + export default jsxRenderer(({ children }, c) => (
110 + <ThemeRoot
111 + theme={config.theme}
112 + lang={c.get("htmlLanguage") ?? config.site.locale}
113 + >
114 + <head>
115 + <RiebeckiteHead
116 + title={config.site.title}
117 + headTags={c.get("headTags") ?? []}
118 + />
119 + <meta name="custom-site-value" content="..." />
120 + </head>
121 + <body class="riebeckite-page rb-site">{children}</body>
122 + </ThemeRoot>
123 + );
124 + ```
125 +
126 + See [Head tag handoff](../framework/honox-integration.en.md#head-tag-handoff)
127 + for the `RiebeckiteHead` pattern and how the site keeps ownership of `<head>`
128 + while delegating standard head rendering to the framework.
129 +
130 + ## Navigation
131 +
132 + The links shown in the Header and Footer come from the
133 + [`@riebeckite/plugin-navigation`](../reference/configuration.en.md#navigation)
134 + plugin, which you register in `riebeckite.config.ts`.
135 +
136 + ```ts
137 + plugins: [
138 + navigation({
139 + items: [
140 + { label: "Guide", href: "/guide" },
141 + {
142 + label: "Notes",
143 + href: "/notes/planning",
144 + children: [
145 + { label: "Planning", href: "/notes/planning" },
146 + { label: "Writing", href: "/notes/writing" },
147 + ],
148 + },
149 + ],
150 + secondary: [{ label: "GitHub", href: "https://github.com/example/site" }],
151 + }),
152 + ]
153 + ```
154 +
155 + Called with no arguments, `navigation()` derives the links from the vault. Pass
156 + `items` to curate them yourself and `secondary` for supplementary links.
157 +
158 + The plugin produces the model and renders each tree through `SiteNav`; the site
159 + decides where the rendered list is placed. In the starter,
160 + `app/components/site-header.tsx` uses `SiteNav`, and `app/routes/_renderer.tsx`
161 + passes the resolved model and the current path in. So it helps to think in terms
162 + of:
163 +
164 + - **Adding or removing a link** → change `navigation({ items })`
165 + - **Deriving links from the vault** → call `navigation()` with no arguments
166 + - **Changing the Header look or HTML** → change `site-header.tsx`
167 + - **Changing where the Header / Footer sits** → change `_renderer.tsx`
168 +
169 + Rendering mechanics — recursive children, active-path detection, locale-aware
170 + normalization, external links, and `aria-current` — live in `SiteNav`, so the
171 + site never has to reimplement them.
172 +
173 + See the [configuration reference](../reference/configuration.en.md#navigation) for
174 + the fields you can set.
175 +
176 + ## Pages and links that plugins provide
177 +
178 + Besides the Header / Footer navigation, plugins can add pages and links. These
179 + fall into two broad kinds.
180 +
181 + ### Pages you browse (Content Discovery)
182 +
183 + For example:
184 +
185 + - Search
186 + - Tag / Folder indexes
187 + - Taxonomy pages
188 + - Feeds
189 + - Sitemap
190 +
191 + Plugins generate the pages or endpoints these need. Enabling a plugin does not
192 + by itself add a link to the Header or Footer. To show one there, add the page to
193 + `navigation` like any other page.
194 +
195 + ### UI that links articles (Content Relationship)
196 +
197 + For example:
198 +
199 + - Breadcrumbs
200 + - Backlinks
201 + - Related Posts
202 + - Series previous/next links
203 + - Local Graph
204 +
205 + These are mostly rendered inside article pages. A UI fragment a plugin provides
206 + can be placed through a **body slot** such as `article.header` or
207 + `article.footer`.
208 +
209 + In short:
210 +
211 + ```text
212 + Header / Footer links
213 + ↓
214 + navigation in riebeckite.config.ts
215 +
216 + Pages such as search, tags, and folders
217 + ↓
218 + plugin page types
219 +
220 + Breadcrumbs, backlinks, and so on
221 + ↓
222 + components / body slots
223 + ```
224 +
225 + ## Adding routes
226 +
227 + `app/routes/` is a normal HonoX route directory, so you can add site-specific
228 + pages as usual. To create `/about`, for example, add it as a HonoX route.
229 +
230 + For Markdown content and pages a plugin provides, you generally do not add a
231 + route yourself. The starter's catch-all route uses `contentRouteSsgParams`,
232 + `riebeniteSsgParams`, and `resolveRiebeckiteContentRequest` to resolve content
233 + and plugin page types automatically.
234 +
235 + To show a plugin page in the Header or Footer, add a link to `navigation` rather
236 + than creating a new route.
237 +
238 + ## 404 (page not found)
239 +
240 + Unknown URLs are handled by HonoX's standard `_404.tsx` route. It is an
241 + ordinary site file, so editing it changes the "page not found" screen:
242 +
243 + ```tsx
244 + // app/routes/_404.tsx
245 + import type { NotFoundHandler } from "hono";
246 +
247 + const handler: NotFoundHandler = (c) => {
248 + c.status(404);
249 +
250 + return c.render(
251 + <main class="not-found">
252 + <h1>Page not found</h1>
253 + <p>The page you requested does not exist or is not available.</p>
254 + <a href="/">Back to home</a>
255 + </main>,
256 + );
257 + };
258 +
259 + export default handler;
260 + ```
261 +
262 + Riebeckite decides that a request is not found, but the response is rendered
263 + through `_renderer.tsx`, so the 404 screen reuses the site's theme, head, Header,
264 + and Footer. Two rules matter:
265 +
266 + - Always keep the status at 404. The generated presets call `c.status(404)`;
267 + a pretty screen served as `200` would be wrong.
268 + - Presentation is site-owned. The markup, copy, links, and CSS are all yours.
269 + Riebeckite does not ship a default 404 component to override.
270 +
271 + The 404 screen only ever sees requests that are not found. Draft, future-dated,
272 + and otherwise unpublished content never reaches it, so a 404 cannot reveal that
273 + private content exists.
274 +
275 + ### Runtime errors (optional)
276 +
277 + Hono's default error handling already logs the error and returns a plain
278 + `500 Internal Server Error`, so a site does not have to add anything.
279 + If you want a site-owned visitor-facing error screen, HonoX supports
280 + `app/routes/_error.tsx` (an `ErrorHandler`). The generated presets do not add
281 + it: configuration, plugin, and build errors are developer-facing and should
282 + stay visible instead of being disguised as a page.
283 +
284 + ## Using plugin components
285 +
286 + Some plugins provide Hono JSX components you can use directly from the site. For
287 + example, you can place Backlinks or a Table of Contents anywhere you like:
288 +
289 + ```tsx
290 + import { Backlinks } from "@riebeckite/plugin-backlinks";
291 + import { TableOfContents } from "@riebeckite/plugin-toc";
292 +
293 + export function ArticleAside({ items, backlinks }: Props) {
294 + return (
295 + <aside>
296 + <TableOfContents items={items} />
297 + <Backlinks backlinks={backlinks} />
298 + </aside>
299 + );
300 + }
301 + ```
302 +
303 + Check each plugin page or package README for the available components and props.
304 + Many components can also be imported as the default export from `./components`:
305 +
306 + ```tsx
307 + import Backlinks from "@riebeckite/plugin-backlinks/components";
308 + ```
309 +
310 + `color-mode` is the exception: it exports `ColorModeScript` and
311 + `ColorModeToggle` from the package root.
312 +
313 + ### Plugins that use body slots
314 +
315 + Some plugins do not place a component directly; instead they add HTML to fixed
316 + locations on the article page. This is the **body slot** mechanism. The fragment
317 + appears automatically once the plugin is enabled and the site's `SiteArticle`
318 + renders the matching slot.
319 +
320 + See [Body slots](../reference/plugin-api.en.md#body-slots) and
321 + [Providing UI or output](../plugins/writing-a-plugin.en.md#providing-ui-or-output).
322 +
323 + ## Building interactive UI
324 +
325 + Create UI that needs browser behavior, such as clicks or state, as a normal
326 + HonoX island under `app/islands/`. Import the island from a route or component
327 + as usual. There is no Riebeckite-specific island mechanism.
328 +
329 + `app/client.ts` initializes both:
330 +
331 + ```ts
332 + createClient();
333 + initRiebeckiteClient();
334 + ```
335 +
336 + `createClient()` initializes the site's client behavior, and
337 + `initRiebeckiteClient()` starts the browser-side behavior that plugins and
338 + themes provide. Site islands live under `app/islands/`, while plugin browser
339 + behavior lives in the plugin, keeping the responsibilities separate.
340 +
341 + ## Changing CSS
342 +
343 + You can change the site design from `app/style.css` and each component's CSS.
344 + Plain CSS and Tailwind both work.
345 +
346 + Import the CSS Riebeckite generates from plugins and themes once from the site
347 + CSS:
348 +
349 + ```css
350 + /* app/style.css */
351 + @import "./.riebeckite/framework-styles.css";
352 + @import "./.riebeckite/plugin-styles.css";
353 + @import "./.riebeckite/theme-styles.css";
354 + ```
355 +
356 + Do not edit files under `app/.riebeckite/`; they are generated. To override
357 + plugin appearance use the `rr-<feature>` root hook, and to adjust the Riebeckite
358 + UI primitives use the `rb-*` structural hooks. See
359 + [CSS hooks](../reference/plugin-api.en.md#css-hooks).
360 +
361 + ## Rules of thumb
362 +
363 + If you are unsure where to make a change, this usually helps:
364 +
365 + | What you want to do | Where to change it |
366 + | --- | --- |
367 + | Add a link to the Header | `riebeckite.config.ts` |
368 + | Change the Header appearance | `app/components/site-header.tsx` |
369 + | Change the site-wide shell | `app/routes/_renderer.tsx` |
370 + | Change the article page structure | `app/components/article.tsx` |
371 + | Add a custom page | `app/routes/` |
372 + | Change the 404 screen | `app/routes/_404.tsx` |
373 + | Create a custom component | `app/components/` |
374 + | Build interactive UI | `app/islands/` |
375 + | Change color or spacing | `app/style.css` |
376 + | Place plugin UI | plugin component / body slot |
377 +
378 + The basic idea is that Riebeckite connects content and plugins to the site, and
379 + **the site decides the final look and structure of each page**.
380 +
381 + ## Where to look next
382 +
383 + - [HonoX Integration](../framework/honox-integration.en.md) — how Riebeckite connects to HonoX and the UI primitives
384 + - [Plugins in Depth](../framework/plugin-system.en.md) — extension points for building plugins
385 + - [Plugin API](../reference/plugin-api.en.md) — the contracts for body slots, pages, assets, and CSS hooks
386 +