A zudo-doc project is configured through one file: the root **`zfb.config.ts`**. It calls `zudoDoc()` with the fields you want to change; every other setting falls back to a documented default.

```ts title="zfb.config.ts"
import { defineConfig } from "zfb/config";
import { zudoDoc } from "@takazudo/zudo-doc/config";

export default defineConfig(
  zudoDoc({
    siteName: "My Docs",
    // …only the fields you chose; everything has a documented @default.
  }),
);
```

`zudoDoc()` merges your fields over the package defaults per field (you win), supplies the package data defaults (frontmatter schema, directive vocabulary, translations, color schemes, tag vocabulary) unless you override them, and returns a **complete `ZfbConfig`** — you spread nothing.

[Dynamic content unavailable in text export]

The `ZudoDocConfig` type is the single source of truth for this reference. Every field carries a `@default` JSDoc annotation that shows on IDE hover; the tables below mirror those defaults verbatim. When this page and the type disagree, the type wins.



[Dynamic content unavailable in text export]

The defaults here are the **package defaults** a fresh `create-zudo-doc` project inherits. This showcase enables far more (most features on, i18n, a full header nav) by passing those fields explicitly — do not read the showcase's rich config as the default baseline.



## Home page

### home

Homepage layout and optional Markdown introduction, applied to `/` and locale-prefixed homes such as `/ja/`.

Default: `{ wide: false, introMarkdown: "", sitemapHeading: "" }`

| Field | Type | Purpose |
|---|---|---|
| `wide` | `boolean` | Widen the outer page container for a larger category grid; defaults to `false`. |
| `introMarkdown` | `string` | Optional prose below the logo, title, short tagline and navigation. |
| `sitemapHeading` | `string` | Plain-text heading above the existing documentation tree. Blank uses the translated default. |

Use `siteDescription` for the short tagline and existing metadata uses; `locales.<code>.description` overrides the tagline. Use `introMarkdown` for longer prose. It does not replace metadata or the tagline. Keep [homeExtras / extras](https://zudo-doc.takazudomodular.com/docs/reference/host-chrome-bindings) for inline navigation additions in the slash-separated links row; they are not a prose slot.

#### Markdown-first example

Edit `zfb.config.ts`; the scaffold's root page can keep its one-line package re-export. The introduction and heading are serializable strings, so a configuration editor can store them without callbacks or executable JavaScript. Fresh scaffolds leave the introduction empty; this showcase's example content is not copied into new projects.

```ts title="zfb.config.ts"
import { defineConfig } from "zfb/config";
import { zudoDoc } from "@takazudo/zudo-doc/config";

export default defineConfig(
  zudoDoc({
    siteName: "My Docs",
    siteDescription: "A short guide to our project.",
    home: {
      wide: false,
      introMarkdown: `## Start here

Learn the **essentials**, then explore the reference.

- [Getting started](docs/getting-started/)
- [Configuration](docs/guides/configuration/)

> [!TIP]
> Keep the introduction focused on your readers' first steps.`,
      sitemapHeading: "Browse the guides",
    },
    locales: {
      ja: {
        label: "JA",
        dir: "src/content/docs-ja",
        description: "プロジェクトの使い方をまとめたガイド。",
        introMarkdown: `## はじめに

**基本的な使い方**を学んでから、リファレンスへ進めます。

- [はじめに](docs/getting-started/)
- [設定](docs/guides/configuration/)

> [!TIP]
> 読者が最初に必要とする情報に絞って紹介しましょう。`,
        sitemapHeading: "ガイドを探す",
      },
    },
  }),
);
```

#### Locale precedence and no-description example

Each defined `locales.<code>.introMarkdown` or `sitemapHeading` overrides its corresponding `home` field. Omit a locale field to inherit the home value. An explicit empty introduction disables fallback; whitespace-only content also renders nothing. These rules are independent of the locale's short `description`.

A blank resolved `sitemapHeading` restores the translated default: “Explore the documentation” in English and “ドキュメントを探す” in Japanese. It does not hide the heading. A blank locale heading restores that locale's default even when the global heading is customized.

For a home without long description prose, use this configuration. The logo, title, tagline and navigation remain; there is no prose wrapper or upper separator, and exactly one separator appears before the independent sitemap H2. To hide the short tagline too, clear `siteDescription` and any locale `description` separately.

```ts
home: { introMarkdown: "", sitemapHeading: "" },
locales: {
  ja: {
    label: "JA",
    dir: "src/content/docs-ja",
    introMarkdown: "",
    sitemapHeading: "",
  },
},
```

#### Supported Markdown and safe restrictions

The introduction uses the standard Markdown/GFM pipeline and shared non-heading typography, with the following field-specific limits. See [Markdown features](https://zudo-doc.takazudomodular.com/docs/markdown-features) for detail-page examples; executable MDX components shown there are not accepted in this string.

| Content | Homepage introduction behavior |
|---|---|
| Paragraphs, emphasis, bold, deletion and line breaks | Supported. |
| Ordered, unordered, nested and task lists | Supported; task checkboxes retain their checked/disabled state. |
| Links, reference links and images | Supported with safe URLs; images are responsive. No filesystem dimension lookup or asset-viewer remapping. |
| Quotes and GitHub alerts | Supported; use `> [!TIP]` and other GitHub alert markers. |
| Inline and fenced code | Supported with production semantic highlighting; code examples stay inert and wide fences scroll internally. |
| Tables and thematic breaks | Supported; table alignment is retained and wide tables scroll internally. |
| Ruby | The non-executable `{base}^{reading}` syntax is supported. |
| Mermaid | Diagram fences use the existing Mermaid initialization when `mermaid: true`; otherwise they remain ordinary code fences. |
| Math | Dollar notation stays literal, including with `math: true`; JSX `MathBlock` is unavailable here. |
| Directives, transclusion and filesystem includes | Rejected even when enabled for document pages; the introduction has no document source path. |
| Raw HTML, JSX, MDX expressions, import/export and YAML frontmatter | Rejected with diagnostics, never executed. Inline code and fences may display them as examples; ordinary non-MDX braces stay inert text. Initial paragraph import/export statements are reserved. |
| Code tabs, advanced fence enrichment, heading markers and reading-time/TOC exports | Not enabled; ordinary fences are supported without promising component parity for advanced metadata. |

Invalid source, unsafe URLs or unsupported generated markup fail the build with a `home.introMarkdown` diagnostic. Nonblank introductions require the `@takazudo/zfb-md-wasm` peer used by the server/build renderer; blank introductions do not load it.

#### Links and images

Author homepage URLs, not source-file links: unlike links inside MDX documents, `.md` and `.mdx` extensions are not rewritten. Relative URLs resolve against the active localized homepage with a trailing slash. For example, with base `/manual/` on the Japanese homepage, `docs/getting-started/` resolves to `/manual/ja/docs/getting-started/`, while `/docs/getting-started/` resolves to `/manual/docs/getting-started/`. Root-relative URLs receive the base once and preserve explicit locale prefixes. Use `/img/logo.svg` for a shared root asset; `img/logo.svg` targets the active locale directory.

HTTP and HTTPS links/images are allowed, and links also allow `mailto:` and `tel:`. Fragments stay fragments, spaces in paths are encoded, and `..` follows normal URL resolution and can leave the base. Protocol-relative URLs, `data:`, `javascript:`, `vbscript:`, control characters and backslashes are rejected.

#### Compact layout and headings

Identity and prose share a centered, fixed **60rem maximum width** (960 CSS px at the standard 16px root size), shrinking to the available space and respecting text-size changes. There is no introduction width option. The logo stays 320 CSS px with a 1200:630 aspect ratio and a 100% maximum width. Desktop places the logo left of left-aligned identity text; mobile stacks and centers the identity. Prose stays left-aligned, and long titles, taglines and links wrap.

With an introduction, full content-width separators surround the padded prose, followed by a separate sitemap H2 and the existing documentation tree. When tags are enabled, the same separator precedes the Tags heading. The separators follow the outer page container, not the 60rem inner cap. `home.wide: true` still widens that outer container and category grid; its behavior is unchanged.

Compared with detail-page headings, compact headings are plain bold text without borders, gradients or hash decorations. H1 Markdown becomes H2 to retain one page H1, and introduction headings never enter the documentation TOC. H2 shares the home section-heading style used by the sitemap and Tags headings — `text-title` (1.4rem by default) at line-height 1.25, with any theme-pack H2 rules, counters or bars removed; H3 uses 1.125rem, H4 1rem, and H5/H6 0.9375rem; body text uses 1rem/1.75. Normal flow gaps are 1rem and heading-adjacent gaps 0.5rem, with section gaps of 1.75rem for H2, 1.5rem for H3, and 1.25rem below that. The first child has no top gap.

### siteTreeNavIgnore

Top-level category slugs to hide from the package-owned home-page category grid on `/` and locale-prefixed home pages such as `/ja`, and from the `<SiteTreeNav />` / `<SiteTreeNavDemo />` MDX tags. This does not affect header nav, sidebar, search, or sitemap.

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    siteName: "My Docs",
    siteTreeNavIgnore: ["inbox", "develop"],
  }),
);
```

The package previously hard-coded `["inbox", "develop"]` for this showcase's own need. The package default is now `[]`; set this field when your site needs to hide categories.

Type: `string[]` · Default: `[]`

### siteTreeNavSecondary

Top-level category slugs to move out of the package-owned home-page category grid on `/` and locale-prefixed home pages such as `/ja`, and into a compact secondary row directly under it. Each listed category renders as a small chevron link with its label, and the row follows the array order rather than the grid's category order. Use it for system-level categories that should stay reachable from the home page without competing with the primary reading paths.

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    siteName: "My Docs",
    siteTreeNavSecondary: ["changelog", "claude", "codex"],
  }),
);
```

- A slug that matches no top-level category is skipped.
- A slug that is also listed in [`siteTreeNavIgnore`](https://zudo-doc.takazudomodular.com/docs/guides/configuration#home-page-sitetreenavignore) is hidden entirely — `siteTreeNavIgnore` wins.
- A category without its own page ([`category_no_page: true`](https://zudo-doc.takazudomodular.com/docs/guides/frontmatter#fields-category_no_page)) has no link target, so it stays in the grid and a development warning is logged.
- An empty list renders no secondary row.

This affects only the home page. The `<SiteTreeNav />` / `<SiteTreeNavDemo />` MDX tags ignore it, and header nav, sidebar, search, and sitemap are unchanged.

Type: `string[]` · Default: `[]`

## Identity and URLs

### siteName

Site name shown in the header and metadata. Page titles render as `{page title} | {siteName}`. The one field you almost always set.

Default: `"Docs"`

### siteDescription

Site description used in metadata (`<meta name="description">`, llms.txt).

Default: `""`

### logo

Home-hero logo. `"auto"` (the default) renders a generated deterministic SVG seeded by `siteName` — a framed "decorated plate" mark that adapts to light/dark automatically, so a fresh project gets a presentable hero with no asset work. Set a path string (e.g. `"/img/logo.svg"`) to render your own asset as a theme-adaptive CSS mask, or `false` to hide the logo block entirely.

Run `npx zudo-doc eject logo` to materialize the generated mark as a real file: it writes `public/img/logo.svg` and flips this field to `"/img/logo.svg"`, so you own an editable asset — tweak it, replace it, or reuse it elsewhere (e.g. for OGP) — instead of the request-time generated default. See [Customizing → Rung 5](https://zudo-doc.takazudomodular.com/docs/reference/customizing#rung-5-%E2%80%94-ejecting-a-component) for the CLI flags.

`logo: ""` throws a `TypeError` at config resolution instead of silently rendering a CSS mask of an empty path — pass `false` to hide the logo, or omit the field for the default.

Default: `"auto"`

### favicon

The `<link rel="icon">` set emitted into every page's `<head>`. Omit it and you get the four-file convention a fresh `create-zudo-doc` project ships in `public/`: `favicon.svg`, `favicon.ico` (`sizes="any"`), `favicon-32x32.png`, `favicon-16x16.png`, in that order.

Type: `string | FaviconConfig | false` · Default: `undefined` (the four-link set above)

| Value | What it emits |
|-------|---------------|
| omitted | The four-link default |
| `"auto"` | One inline SVG `data:` URL icon generated from `siteName` — no asset files needed |
| any other string | One link to that path, with `type` inferred from the file extension |
| a `FaviconConfig` object | Only the slots you supply, always in `svg` → `ico` → `png32` → `png16` order |
| `false` | No favicon links at all |
| `""` | Throws a `TypeError` at config resolution — see below |

**The empty string is rejected, not emitted.** Per the HTML spec an empty `href` resolves to the *current document*, so `favicon: ""` would silently make every page fetch its own HTML as a "favicon" — a wasted request and a garbage tab icon. `zudoDoc({ favicon: "" })` throws a `TypeError` naming the field instead; pass `false` for no favicon links, or omit the field for the default four-link set. The same rejection applies to an empty-string `FaviconConfig` slot (e.g. `favicon: { ico: "" }`) — the error names the specific slot (`favicon.ico`). An empty *object* (`favicon: {}`, no slots at all) is unaffected and still validly emits nothing. Only the exact empty string is rejected — a whitespace-only value like `" "` is passed through as-is.

**The default is a convention, not a check.** It emits all four links whether or not the files exist — so a project whose `public/` has no `favicon.ico` still ships `<link rel="icon" href="/favicon.ico" sizes="any">` on every page, and every visitor's browser console logs a 404 for it. Supplying only the slots you actually have is the fix:

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    siteName: "My Docs",
    // No favicon.ico in public/, so no ico link — and no console 404.
    favicon: {
      svg: "/favicon.svg",
      png32: "/favicon-32x32.png",
      png16: "/favicon-16x16.png",
    },
  }),
);
```

`FaviconConfig` is `{ svg?: string; png32?: string; png16?: string; ico?: string }`. Every key is optional, the order you write them in does not matter (emission order is fixed), and `{}` emits nothing at all — the same result as `false`.

A plain string collapses the set to a single link and infers `type` from the extension: `.svg`, `.png`, `.ico`, `.jpg` / `.jpeg`, `.gif`, `.webp`, `.avif`. Anything else omits `type` and lets the browser sniff. A `?query` or `#hash` suffix is ignored when inferring, so `"/icon.svg?v=2"` still gets `type="image/svg+xml"`.

```ts
favicon: "/icon.png", // one link, type="image/png"
favicon: false,       // no favicon links at all
```

`favicon: "auto"` needs no asset files at all: it emits one SVG favicon as an inline `data:` URL, generated deterministically from `siteName` with the same seeded glyph as [`logo: "auto"`](https://zudo-doc.takazudomodular.com/docs/guides/configuration#identity-and-urls-logo), so the hero logo and the browser-tab icon show the same mark. The favicon variant is a square, opaque, fixed light-palette plate — a tab icon cannot follow the page's color mode the way the masked hero logo does.

[Dynamic content unavailable in text export]

SVG favicon support is still uneven across browsers (Safari most notably). `"auto"` degrades to the browser's default blank icon rather than a broken one, but a project that wants maximal coverage should ship real `.ico` / `.png` files and use the object form.



Href values starting with `/` are prefixed with the configured [`base`](https://zudo-doc.takazudomodular.com/docs/guides/configuration#identity-and-urls-base), so `"/favicon.svg"` resolves to `/pj/my-site/favicon.svg` on a sub-path deployment. Anything else — an absolute `https://…` URL, a `data:` URL — is emitted verbatim.

[Dynamic content unavailable in text export]

[`head.alternateLinks`](https://zudo-doc.takazudomodular.com/docs/guides/configuration#identity-and-urls-head) cannot replace this set. Those entries render **after** the favicon block, so a `{ rel: "icon" }` entry there only appends one more link — it can never remove or override one of these. `favicon` is the only way to change what the set contains.



### siteUrl

Canonical site origin (e.g. `"https://example.com"`) for sitemap / canonical / `og:url`. Empty = not set.

Default: `""`

### base

Public URL sub-path prefix mounted in front of every absolute asset URL (e.g. `"/pj/my-site/"`). `"/"` = root-mounted. All internal links (sidebar, nav, prev/next, search) are prefixed automatically.

Default: `"/"`

[Dynamic content unavailable in text export]

Inline markdown links in MDX (e.g. `[text](/docs/some-page)`) are **not** rewritten for a non-root base — use relative links in content instead.



### trailingSlash

Append a trailing `/` to extensionless internal hrefs.

Default: `false`

See [Trailing-Slash Policy](https://zudo-doc.takazudomodular.com/docs/develop/trailing-slash-policy) for how the rule splits between build time and the deployment host.

### minifyHtml

Minify production HTML output from `zfb build`. Set `false` for readable output while debugging.

Default: `true`

### githubUrl

GitHub repository URL shown in the header (via the `github-link` header item), or `false` to omit.

Type: `string | false` · Default: `false`

### editUrl

"Edit this page" link base, or `false` to omit. The full URL is `editUrl + contentDir + "/" + entryId`.

Type: `string | false` · Default: `false`

### noindex

Add `noindex,nofollow` to every page (for internal docs).

Default: `false`

See [Avoid Robots Indexing](https://zudo-doc.takazudomodular.com/docs/guides/avoid-robots-indexing).

### head

Site-wide custom `<head>` extras (preconnect / preload / stylesheets / meta / alternateLinks). Omit to emit nothing extra.

Type: `SiteHeadConfig` · Default: `undefined`

### metaTags

`<meta>` / OpenGraph / Twitter-card emission toggles.

Default: `{ description: true, keywords: false, ogImage: false, ogSiteName: true, twitterCard: false }`

| Property | Type | Description |
|----------|------|-------------|
| `description` | `boolean` | Emit `<meta name="description">` |
| `keywords` | `string \| false` | `<meta name="keywords">` value, or omit |
| `ogImage` | `string \| false` | Path for `og:image` / `twitter:image`, or omit |
| `ogSiteName` | `boolean` | Emit `og:site_name` |
| `twitterCard` | `"summary" \| "summary_large_image" \| false` | Twitter Card type, or omit the block |
| `twitterSite` | `string` (optional) | `twitter:site` handle |
| `twitterCreator` | `string` (optional) | `twitter:creator` handle |

### sitemap

Emit a sitemap route.

Default: `false`

**Set `sitemap: true` if you want one.** At the default `false`, no `/sitemap.xml` route is emitted at all — the URL 404s. Setting `siteUrl` is not enough on its own: configuring `siteUrl` only for canonical / `og:url` is a perfectly normal setup, so nothing is inferred from it and no build-time warning nudges you. This reference is where that contract lives.

Serving nothing is deliberate. An empty `<urlset>` at a well-known URL is not a neutral placeholder — it is a positive assertion to crawlers that the site has no indexable URLs, which is worse than the URL simply not existing. The generated `robots.txt` already drops its `Sitemap:` line in this state, so the two artifacts now agree instead of contradicting each other. Earlier versions did emit the empty `<urlset>`; they no longer do.

The gate lives in the package's route injection (`packageOwnedRoutes`, on by default). A project that hand-wires its own `pages/sitemap.xml.tsx` sits outside it and keeps whatever that page returns — and if that page re-exports the package entrypoint while `sitemap` is `false`, the build warns and still writes the empty `<urlset>`.

See also the [SEO guide](https://zudo-doc.takazudomodular.com/docs/guides/seo) for how `metaTags` and `sitemap` combine into a complete social-share + search-indexing setup.

### onBrokenMarkdownLinks

What to do when a `.md`/`.mdx` link cannot be resolved: `"warn"` logs and continues, `"error"` fails the build, `"ignore"` is silent.

Type: `"warn" | "error" | "ignore"` · Default: `"warn"`

This setting also governs the post-build existence check for raw `<img src>` attributes in rendered HTML. `"error"` reports problems and fails the build, `"warn"` reports and continues, and `"ignore"` skips the scan entirely.

The scan checks site-absolute paths beginning with `/`. It strips queries and fragments, decodes URL escapes, and checks for a file in the build output. With `base: "/manual/"`, `/manual/img/logo.svg` maps to `dist/img/logo.svg`. Paths outside that base, missing files, and paths escaping the output directory are reported as broken.

External URLs, protocol-relative `//` URLs, schemes such as `data:`, and relative paths are excluded. Comments, script strings, and escaped code examples are not treated as images. `srcset` is not checked.

## Color

### colorScheme

Active color scheme name (must exist in `colorSchemes`). The two bundled schemes are `Default Light` and `Default Dark`.

Default: `"Default Dark"`

### colorMode

Light/dark mode wiring, or `false` for a single fixed scheme.

Default: `{ defaultMode: "dark", lightScheme: "Default Light", darkScheme: "Default Dark", respectPrefersColorScheme: true }`

| Property | Type | Description |
|----------|------|-------------|
| `defaultMode` | `"light" \| "dark"` | Light/dark fallback when system following is disabled and no choice is saved |
| `lightScheme` | `string` | Scheme used in light mode |
| `darkScheme` | `string` | Scheme used in dark mode |
| `respectPrefersColorScheme` | `boolean` | Use System as the first-visit choice when no preference is saved |

With the default `respectPrefersColorScheme: true`, a first visit with no saved choice starts in **System** and follows the device's light/dark preference. The compact header appearance menu offers **Light**, **Dark**, and **System**; its icon shows the selected preference (sun for Light, moon for Dark, monitor for System), not the next action. The preference is saved in `localStorage` under `zudo-doc-theme` and remains selected after reload.

`defaultMode` remains a two-value light/dark fallback. If you set `respectPrefersColorScheme: false`, a first visit with no saved choice uses `defaultMode`; a visitor can still explicitly select System, which continues to follow the device. `colorMode: false` keeps a single fixed scheme and has no appearance menu.

```ts
colorMode: false, // single fixed scheme (colorScheme only)
```

See the [Color reference](https://zudo-doc.takazudomodular.com/docs/reference/color) for the ramp-native model and adding a custom scheme (via the `colorSchemes` escape hatch below).

## Theme packs

Theme packs are installable design bundles layered **on top of** the color-scheme system above — every pack defines both its light and dark values, so the appearance menu keeps working on any pack. See the [Theme Packs reference](https://zudo-doc.takazudomodular.com/docs/reference/theme-packs) for the switcher UI, the theme CLI, and pack authoring.

### themePack

Active theme pack slug. `"default"` is the stock zudo-doc look (no pack stylesheet loaded). Must be a member of the resolved `themePacks` list; an unknown slug fails the build loudly.

Default: `"default"`

### themePackSwitcher

Mount the bottom-right theme-pack switcher flyout (and its browse-all dialog) on every page.

Default: `false`

### themePacks

Enabled pack slugs, in switcher order — the list order is the switcher's Prev/Next cycle order and the browse-all grid order. `undefined` enables all bundled packs: `"default"` first, then the rest alphabetically. An explicit list is authoritative — it may omit `"default"` and reorder freely; duplicates or unknown slugs fail the build loudly.

Default: `undefined`

```ts
themePacks: ["default", "foundry"],
```

## Content and i18n

### docsDir

Directory (project-root-relative) holding the default-locale docs.

Default: `"src/content/docs"`

### entryDocSlug

Route slug of the doc page the [versions page](https://zudo-doc.takazudomodular.com/docs/guides/versioning#versions-listing-page) links to as the "latest docs" entry point (and each past version's docs link). No leading or trailing slashes — e.g. `"getting-started"` or `"overview/getting-started"`. Not validated at runtime; an invalid slug simply 404s.

Default: `"getting-started"`

### dateFormat

Date-display format. `"locale"` keeps the built-in `Intl` rendering; anything else is a token pattern.

Type: `DateFormatSetting` · Default: `"locale"`

zudo-doc renders dates in five distinct **shapes**, so the setting is keyed by shape — called a *role* — rather than by call site. Nobody wants the pager date to differ from the header date, but a compact card stamp genuinely differs from a full date line.

| Role | Default (`en`) | Default (`ja`) | Where it appears |
|---|---|---|---|
| `full` | `Aug 22, 2026` | `2026年8月22日` | H1 date line, git metainfo, pager, asset page, revision history, note-tray list and timeline, ungrouped site-tree rows |
| `monthDay` | `Aug 22` | `8月22日` | Note-tray card grid |
| `year` | `2026` | `2026年` | Note-tray card grid, year-group headings |
| `yearMonth` | `2026 August` | `2026年8月` | Note-tray, sidebar and site-tree month-group headings |
| `numericMonthDay` | `08-22` | `08-22` | Sidebar tray rows, site-tree grouped rows |

`numericMonthDay` is deliberately locale-insensitive at its default: it is a compact sort-aligned stamp, not prose, so it renders `08-22` in every locale. Give it a pattern and it formats like any other role.

[Dynamic content unavailable in text export]

Year-group headings in the sidebar and the site tree print the grouping key itself — `2026` in every locale — because a year heading has never gone through a formatter, and routing it through one would silently turn existing Japanese sites into `2026年`. The note-tray card grid does format its year, so a Japanese page shows `2026年` there. Set `year` to a pattern and both surfaces follow it; headings apply the pattern against January 1 of that year, so month or day tokens in a `year` pattern render that boundary date.



#### A bare string sets only `full`

This is the setting's one real surprise. A bare pattern is shorthand for `{ full: "…" }`, so it changes the H1 date line, the pager and the other `full` surfaces — and leaves card stamps, month headings and sidebar rows exactly as they were:

```ts
dateFormat: "YYYY/MM/DD",
```

To change those too, name their roles.

#### Roles and per-locale overrides

The object form takes the five role keys plus an optional `locales` map keyed by locale code, whose values take the same five keys:

```ts
dateFormat: {
  full: "MM/DD/YYYY",
  yearMonth: "MM/YYYY",
  locales: {
    ja: { full: "YYYY年M月D日" },
  },
},
```

Resolution precedence, evaluated **per role**:

1. `locales[locale][role]` — the per-locale override
2. `[role]` — the top-level role
3. `"locale"` — the built-in `Intl` rendering

Per-locale overrides are the point of the object form on a multilingual site: `Aug 22, 2026` and `2026年8月22日` are both correct for their locale, and no single pattern serves both. See [Internationalization](https://zudo-doc.takazudomodular.com/docs/guides/i18n#date-formats-per-locale).

#### Supported tokens

| Token | Meaning | `2026-08-05` (`en`) |
|---|---|---|
| `YYYY` | 4-digit year | `2026` |
| `YY` | 2-digit year | `26` |
| `MMMM` | Full month name (locale-aware) | `August` |
| `MMM` | Short month name (locale-aware) | `Aug` |
| `MM` | Zero-padded month | `08` |
| `M` | Month | `8` |
| `DD` | Zero-padded day | `05` |
| `D` | Day | `5` |
| `[…]` | Literal escape — `[at] YYYY` | `at 2026` |

Matching rules worth knowing:

- Anything that is not a token passes through literally, so `/`, `-`, `.`, spaces and `年 月 日` need no escaping.
- The longest token wins at each position: `YYYY` beats `YY`, and `MMMM` beats `MMM`, `MM` and `M`.
- A substituted month name is never rescanned, so `March` and `May` keep their `M`.
- An unrecognized run degrades instead of failing the build: `YYYYY` renders the year followed by a literal `Y`, and `dddd` stays `dddd`.
- Brackets do not nest — the first `]` closes the escape — and an unmatched `[` is emitted literally while the rest of the pattern still resolves.
- `MMM` and `MMMM` produce localized month names for `en`, `ja` and `de` only; every other locale falls back to English names. See [Date formats per locale](https://zudo-doc.takazudomodular.com/docs/guides/i18n#date-formats-per-locale).

[Dynamic content unavailable in text export]

The token spellings are borrowed from Day.js because they are familiar, but this is a bounded vocabulary, not Day.js compatibility. The eight tokens above and `[…]` escapes are the whole set: there are no weekday, time, quarter, or era tokens and no plugin surface. Everything else is emitted literally.



#### Examples

A bare pattern — the `full` role in every locale. The H1 date line and pager read `2026/08/22`; card stamps, month headings and sidebar rows are untouched:

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    dateFormat: "YYYY/MM/DD",
  }),
);
```

Two roles, every locale. `full` reads `08/22/2026` and `yearMonth` reads `08/2026`; `monthDay`, `year` and `numericMonthDay` stay at `"locale"`:

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    dateFormat: {
      full: "MM/DD/YYYY",
      yearMonth: "MM/YYYY",
    },
  }),
);
```

Per-locale. English pages read `Aug 22, 2026` and Japanese pages `2026年8月22日`; `yearMonth` is overridden on `ja` only, so English keeps its default `2026 August` while Japanese reads `2026年8月`:

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    dateFormat: {
      full: "MMM D, YYYY",
      locales: {
        ja: { full: "YYYY年M月D日", yearMonth: "YYYY年M月" },
      },
    },
  }),
);
```

#### An object replaces the default wholesale

`zudoDoc()` merges your fields over the package defaults at the **top level only**, so a supplied `dateFormat` object replaces the default `"locale"` outright rather than being deep-merged into it. That costs nothing here: the resolver fills every role you leave out with `"locale"` anyway, which is exactly what the default meant.

#### What it does not affect

`dateFormat` is a display setting. It does not touch the `YYYY-MM-DD` [frontmatter authoring format](https://zudo-doc.takazudomodular.com/docs/guides/frontmatter#fields-date), the machine-readable `datetime` attributes on `<time>` elements, the sitemap's `lastmod` values, or the grouping keys behind year and month headings — all of those stay ISO.

### defaultLocale

Default locale code (unprefixed routes).

Default: `"en"`

### locales

Additional locales: code → `{ label, dir, description?, introMarkdown?, sitemapHeading? }`. Each becomes a `/<code>/docs/…` route tree and a `docs-<code>` collection. The map is ordered: the language switcher renders every configured `label` in that order, so labels are not hard-coded to `JP` or `JA`.

Default: `{}`

```ts
locales: {
  ja: {
    label: "JA",
    dir: "src/content/docs-ja",
    description: "プロジェクトの使い方をまとめたドキュメント。",
  },
  de: { label: "DE", dir: "src/content/docs-de" },
},
```

`description` is an optional string for that locale’s home page hero. When omitted, it falls back to `settings.siteDescription`; an empty string (`""`) intentionally leaves the hero description blank. `settings.locales` contains only non-default locales, so the default locale always uses `settings.siteDescription` and has no locale-specific override.

Current limitations: this field affects only the home page hero description. `llms.txt` still uses the global `siteDescription` for every locale, and home pages still emit no `<meta name="description">`.

Locale codes are used as URL segments and directory suffixes. Keep them
lowercase and path-safe; duplicate codes, the primary `defaultLocale`, path
separators, and traversal segments are rejected by `create-zudo-doc` before it
writes files. The generated `ja` tree starts with Japanese prose; other
arbitrary locale trees start with English placeholder prose for translation.

### defaultLocaleOnlyPrefixes

Route prefixes served only in the default locale (never locale-prefixed). The language switcher omits them on affected pages.

Default: `[]`

See [Default-locale-only prefixes](https://zudo-doc.takazudomodular.com/docs/guides/i18n#default-locale-only-prefixes).

### versions

Docs versions, or `false` for a single (unversioned) doc set. Each versioned section is served at `/v/{slug}/docs/`.

Type: `VersionConfig[] | false` · Default: `false`

| Property | Type | Description |
|----------|------|-------------|
| `slug` | `string` | Version identifier in the URL path |
| `label` | `string` | Version-switcher display label |
| `docsDir` | `string` | Content directory for this version's default-locale docs |
| `locales` | `Record` (optional) | Per-locale content dirs for this version |
| `banner` | `"unmaintained" \| "unreleased" \| false` (optional) | Banner shown on this version's pages |

See the [Versioning guide](https://zudo-doc.takazudomodular.com/docs/guides/versioning).

### mermaid

Enable mermaid diagram rendering in markdown.

Default: `true`

### transclude

Enable `:::include{file="…"}` transclusion. zudo-doc passes the enabled boolean to zfb as the object-typed `markdown.features.transclude: {}` setting; configure it here rather than replacing the package-owned Markdown feature block.

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    transclude: true,
  }),
);
```

Type: `boolean` · Default: `false`

See [Transclude](https://zudo-doc.takazudomodular.com/docs/markdown-features/transclude) for the directive syntax, filesystem restrictions, and build-fatal failure behavior.

### math

Enable KaTeX math rendering (`$…$`, `$$…$$`, fenced `math`).

Default: `false`

`math: true` requires installing the optional peer dependency `katex` (`pnpm add katex`) — `create-zudo-doc` does not add it for you, and it is absent from a fresh scaffold's `package.json`. `katex` is an optional peer of `@takazudo/zudo-doc`: the build stays green without it, but rendering a `<MathBlock>` throws at request time. See [Math Equations](https://zudo-doc.takazudomodular.com/docs/components/math-equations) for the component. `docHistory`'s Compare view has the matching contract with `diff` — see [docHistory](https://zudo-doc.takazudomodular.com/docs/guides/configuration#site-settings-dochistory) below.

### cjkFriendly

Enable zfb's CJK-friendly line-break / emphasis handling.

Default: `false`

## Tags

`tagVocabulary` and `tagGovernance` are **orthogonal** — one is the runtime gate, the other the enforcement level. The vocabulary *entries* are supplied separately via the [`tagVocabularyEntries`](https://zudo-doc.takazudomodular.com/docs/guides/configuration#escape-hatch-fields) escape hatch.

### docTags

Enable the `/docs/tags` + `/docs/tags/[tag]` tag index routes.

Default: `false`

### tagPlacement

Where per-page tags render relative to the content.

Type: `"after-title" | "before-pager"` · Default: `"after-title"`

### tagGovernance

Tag-governance enforcement level when the vocabulary is consulted (`"off"` / `"warn"` / `"strict"`).

Default: `"off"`

### tagVocabulary

Whether the tag vocabulary is consulted at runtime (exact-id recognition and grouped footer). **Orthogonal to `tagGovernance`.** This is the boolean gate — the entries themselves come from `tagVocabularyEntries`.

Default: `false`

See [Tag governance](https://zudo-doc.takazudomodular.com/docs/guides/tag-governance).

## Table of contents and headings

### tocMinDepth

Minimum heading depth included in the TOC (2–4).

Default: `2`

### tocMaxDepth

Maximum heading depth included in the TOC (2–4).

Default: `4`

## Search

### searchMaxBodyLength

Maximum number of characters of each page's body indexed for search matching.

Type: `number` · Default: `3000`

This controls how deep into a page a search query can match, not how much of a result is displayed — results always show a compact snippet centered on the match; see [Site Search](https://zudo-doc.takazudomodular.com/docs/guides/search). Raising the cap grows `search-index.json` accordingly: for example, raising it from the previous 300-character default to 3,000 grew this showcase's index about 3.4× (roughly 410 KB to 1.4 MB). Invalid values (non-integer or `<= 0`) throw at config time.

## Site Settings

### designTokenPanel

Enable the interactive Design Token Panel (zdtp) for live editing of spacing, font, size, and color tokens.

Default: `false`

The panel works with zero extra config **in a project scaffolded with the feature on** — that is what installs the optional `@takazudo/zdtp` peer and adds its `@import "@takazudo/zdtp/styles.css";` line. Turning this on in a project scaffolded without it needs both of those steps by hand; otherwise the panel's lazy `import("@takazudo/zdtp")` fails at runtime and only logs a warning. To fully customize it, point `designTokenPanelConfigModule` at a host module — see [Host Chrome Bindings](https://zudo-doc.takazudomodular.com/docs/reference/host-chrome-bindings#the-design-token-panel-channel-designtokenpanelconfigmodule).

### sidebarResizer

Enable the draggable sidebar resizer.

Default: `false`

### sidebarToggle

Enable the desktop sidebar collapse toggle.

Default: `false`

### tocToggle

Enable the desktop table-of-contents collapse toggle — a chevron button pinned to the right edge of the viewport on xl screens (1280px and up).

Default: `false`

Collapsing the TOC hands its width to the content column, so the same page can be read at a wider measure. That is the point of the feature: wide tables and long code lines stop wrapping or scrolling sideways once the TOC is out of the way. The reader's choice is remembered, so it holds across pages and across visits.

The toggle drives the **package default TOC** only. If you replace the `Toc` slot through `chromeBindingsModule`, your own component renders unchanged and no toggle appears — see [Host Chrome Bindings](https://zudo-doc.takazudomodular.com/docs/reference/host-chrome-bindings).

[Dynamic content unavailable in text export]

They solve different problems and can be used together. The [`hide_toc`](https://zudo-doc.takazudomodular.com/docs/guides/frontmatter#fields-hide_toc) frontmatter is an **author** decision baked in at build time: the page ships without a TOC, and no reader can bring it back. `tocToggle` is a **reader** control: wherever the default TOC is rendered, the reader decides whether to see it right now. It changes nothing on a page that already opted out with `hide_toc`.



### imageEnlarge

Enable click-to-enlarge for content images.

Default: `false`

### assetViewer

Generate a default-locale viewer page for every included file under `public/<assetViewerDir>/` and enable manifest-backed asset links, cards, code excerpts, and image-caption links in MDX.

Default: `false`

See [Asset Viewer](https://zudo-doc.takazudomodular.com/docs/guides/asset-viewer) for the directory layout, viewer behavior, and author entry points.

### assetViewerDir

Set the directory below `public/` that contains viewer-managed files. The same value becomes the raw public URL prefix.

Default: `"assets"`

The value must be a safe relative URL path and must differ from `assetViewerRoutePrefix`.

### assetViewerRoutePrefix

Set the URL prefix for generated viewer pages.

Default: `"files"`

With the defaults, `public/assets/guide.pdf` is raw at `/assets/guide.pdf` and has a viewer at `/files/guide.pdf/`.

### assetViewerExclude

Exclude asset-relative glob paths from viewer generation while leaving the underlying files in `public/`.

Type: `string[]` · Default: `[]`

For example, `["**/*.map", "drafts/**"]` skips source maps and everything below the asset directory's `drafts/` folder.

### assetViewerIndex

Generate an asset listing page at `/<assetViewerRoutePrefix>/` when the asset viewer is enabled.

Default: `false`

See [Asset Viewer](https://zudo-doc.takazudomodular.com/docs/guides/asset-viewer#the-assets-index-page) for the listing behavior and header-navigation setup.

### assetViewerIndexing

Independently opt asset viewer pages into the search index, `llms.txt`, and the sitemap. `assetViewer: true` is required; indexing subkeys have no effect when the viewer is disabled.

Type: `false | { search?: boolean; llmsTxt?: boolean; sitemap?: boolean }` · Default: `false`

Each subkey is off unless it is explicitly `true`, including when the setting is a partial object. `search` adds asset pages to `search-index.json` with `asset:`-prefixed IDs. `llmsTxt` adds a `## Files` section to `llms.txt` and appends asset pages to `llms-full.txt`; text-like assets include up to 8 KB of body text and then an explicit truncation marker, while binary assets are one-line stubs with no body. `sitemap` adds the `/files/...` routes.

See [Asset Viewer](https://zudo-doc.takazudomodular.com/docs/guides/asset-viewer#opting-asset-pages-into-indexing) for a configuration example. This showcase enables all three subkeys.

### findInPage

Mount the `FindInPageInit` island (Cmd/Ctrl+F find bar) in the body-end islands.

Default: `false`

Auto-enabled by `create-zudo-doc`'s tauri feature for generated Tauri scaffolds (the generator emits `findInPage: true` when the tauri feature is selected; it ships with the next `create-zudo-doc` release). Self-gates on `window.__TAURI_INTERNALS__`, so it is a safe no-op outside a Tauri shell even when `true`. A host that overrides the `BodyEndIslands` chrome-bindings slot (part of `ChromeHostBindings` — see [Host Chrome Bindings](https://zudo-doc.takazudomodular.com/docs/reference/host-chrome-bindings) for the general seam) must mount the island itself — the package default only applies when the slot is left unset.

### dynamicPageTransition

Enable the dynamic (view-transition) page-loading overlay.

Default: `false`

### frontmatterPreview

Frontmatter-preview config (ignore-key overrides), or `false` to disable the preview panel.

Type: `FrontmatterPreviewConfig | false` · Default: `false`

| Property | Type | Description |
|----------|------|-------------|
| `ignoreKeys` | `string[]` (optional) | Replaces the default ignore list |
| `extraIgnoreKeys` | `string[]` (optional) | Adds to the defaults (ignored if `ignoreKeys` is set) |

See the [Frontmatter Preview reference](https://zudo-doc.takazudomodular.com/docs/reference/frontmatter-preview).

### docHistory

Enable per-page git history (Created/Updated + dropdown). In dev, history is served by `@takazudo/zudo-doc-history-server` on port 4322; in CI a generator emits static JSON.

Default: `false`

[Dynamic content unavailable in text export]

Fresh scaffolds wire the real scanner-reachable `DocHistory` island into every generated document-route shape when this feature is selected. No `chromeBindingsModule` or route edit is required for the history button itself.



The history dropdown's Compare view requires the optional peer dependency `diff`; the build itself does not. `create-zudo-doc` adds `diff` to `package.json` automatically whenever `docHistory` is selected — directly, or indirectly through `bodyFootUtilArea`, which forces `docHistory` on. `diff` is an optional peer of `@takazudo/zudo-doc`, so a project that enables `docHistory` by hand in `zfb.config.ts` (without going through the generator) must `pnpm add diff` itself. See [`math`](https://zudo-doc.takazudomodular.com/docs/guides/configuration#content-and-i18n-math) above for the matching contract with `katex`.

See the [Document History guide](https://zudo-doc.takazudomodular.com/docs/guides/doc-history).

### docHistoryUi

Controls the History button and DocHistory island, postBuild history JSON generation, and dev history proxy. Setting it to `false` keeps the preBuild metadata manifest when `docHistory: true`. The view-source link is retained too.

Type: `boolean` · Default: `true`

Set this by hand in `zfb.config.ts`; there is no new CLI flag. See the [Document History guide](https://zudo-doc.takazudomodular.com/docs/guides/doc-history) for environment-variable interactions and CI job cleanup.

### docMetainfo

Show the Created/Updated/Author meta block on doc pages (extracted from git history at build).

Default: `false`

[Dynamic content unavailable in text export]

The `docHistoryMeta` slot defaults to `{}`. Point `chromeBindingsModule` at a module that binds it to the build-time history manifest. Fresh route stubs already consume that module; do not edit them. See [Custom Components](https://zudo-doc.takazudomodular.com/docs/guides/custom-components) and [Host Chrome Bindings](https://zudo-doc.takazudomodular.com/docs/reference/host-chrome-bindings).



The binding module must provide `docHistoryMeta`; with the default `{}` data slot, there is no metadata block to render. Follow the [Document History guide](https://zudo-doc.takazudomodular.com/docs/guides/doc-history) for the paired `docHistory` setup.

### docMetainfoFields

Select the fields shown in the metadata block. Requires `docMetainfo: true` and a `docHistoryMeta` binding that supplies data.

Type: `Array<"created" | "updated" | "author">` · Default: `["created", "updated", "author"]`

Omitting this enables all fields. Display order stays Created, Updated, Author regardless of array order. When Created is also displayed, Updated is suppressed when its formatted display value equals Created (the configured date format can include a time). With `["updated"]`, Updated appears even when the dates match. An empty array `[]` renders no metadata block.

```ts
docMetainfo: true,
docMetainfoFields: ["updated"],
```

Set this by hand in `zfb.config.ts`; there is no new CLI flag. Selecting fewer fields does not stop history data generation.

### bodyFootUtilArea

Body-foot utility area (doc-history / view-source), or `false` to disable.

Type: `BodyFootUtilAreaConfig | false` · Default: `false`

| Property | Type | Description |
|----------|------|-------------|
| `docHistory` | `boolean` (optional) | Show the History button (requires `docHistory: true` and `docHistoryUi: true`) |
| `viewSourceLink` | `boolean` (optional) | Show a raw-source link (requires `githubUrl`) |

### htmlPreview

Global HTML-preview sandbox config, or `undefined` to disable global injection into every `<HtmlPreview>` iframe.

Type: `HtmlPreviewConfig | undefined` · Default: `undefined`

| Property | Type | Description |
|----------|------|-------------|
| `head` | `string` (optional) | Raw HTML injected into `<head>` |
| `css` | `string` (optional) | CSS injected as a `<style>` block |
| `js` | `string` (optional) | JS injected before `</body>` |

### footer

Footer config (link columns / copyright / taglist), or `false` for no footer.

Type: `FooterConfig | false` · Default: `false`

| Property | Type | Description |
|----------|------|-------------|
| `links` | `FooterLinkColumn[]` | Link columns |
| `copyright` | `string` (optional) | Copyright text (HTML allowed) |

Each `FooterLinkColumn` has `title`, `items` (`{ label, href }[]`), and optional per-locale overrides.

### headerNav

Primary header navigation items.

Default: `[]`

| Property | Type | Description |
|----------|------|-------------|
| `label` | `string` | Display text |
| `labelKey` | `string` (optional) | i18n translation key (overrides `label`) |
| `path` | `string` | Target path |
| `categoryMatch` | `string` (optional) | Links this tab to a sidebar category |
| `versioned` | `boolean` (optional) | Whether this item's link carries the active `/v/{version}` prefix. Default `true` — see [Header Navigation](https://zudo-doc.takazudomodular.com/docs/guides/header-navigation#properties-versioned) |

```ts
headerNav: [
  { label: "Guides", path: "/docs/guides", categoryMatch: "guides" },
  { label: "Reference", path: "/docs/reference", categoryMatch: "reference" },
],
```

See [How to Structure Navigations](https://zudo-doc.takazudomodular.com/docs/getting-started/structuring-navigations).

### headerRightItems

Header right-hand-side items (toggles, switchers, links), in order.

Default: `[{ type: "component", component: "theme-toggle" }]`

| `type` | Extra fields | Description |
|--------|--------------|-------------|
| `"component"` | `component` — `"theme-toggle"` (the Light / Dark / System appearance menu), `"language-switcher"`, `"version-switcher"`, `"github-link"`, `"search"` | Built-in header component |
| `"trigger"` | `trigger` — `"design-token-panel"`, `"ai-chat"` | Opens a panel (requires the feature) |
| `"link"` | `href`, `label?`, `ariaLabel?`, `icon?` | Custom link |
| `"html"` | `html` | Raw HTML |

## AI assistant

### aiAssistant

Enable the AI-chat assistant route (`/api/ai-chat`, SSR).

Default: `false`

Running a live assistant requires a deploy adapter, per-IP KV, an Anthropic secret, and the
`AI_CHAT_DAILY_SPEND_CAP` Durable Object migration when the exact cap is enabled — see
[Customizing → Deploy path](https://zudo-doc.takazudomodular.com/docs/reference/customizing#rung-8-%E2%80%94-the-deploy-path-an-explicit-extend-step)
and the [AI Assistant API](https://zudo-doc.takazudomodular.com/docs/reference/ai-assistant-api). These are host-owned pieces: the
package/scaffold supplies a safe placeholder route, not the showcase's live handler, Worker entry,
or Durable Object class.

### aiChatDemoMode

Short-circuit `/api/ai-chat` with a fixed "disabled" reply (no API key, KV, Durable Object, rate limiter, or provider fetch touched). Set `false` to enable live Claude-backed chat.

Default: `false`

### aiChatAllowedOrigins

Allowed CORS origins for `/api/ai-chat` when not in demo mode. Empty = block all cross-origin browser requests.

Default: `[]`

### aiChatGlobalDailyLimit

Exact Anthropic fetch-admission limit per UTC day across all IPs, or `false` to skip the exact
cap. Provider/network failure does not refund an admitted slot.

Type: `number | false` · Default: `false`

## Generation and integrations

### llmsTxt

Emit an `llms.txt` route summarising the docs.

Default: `false`

See [llms.txt](https://zudo-doc.takazudomodular.com/docs/guides/llms-txt).

### agentExport

Publish a versioned, read-only static corpus at `<base>/agent/v1/` for documentation clients. It includes a manifest, a complete agent search index, fetchable item JSON, and per-page Markdown. This works on any static host.

Default: `false`

See [Agent-readable export](https://zudo-doc.takazudomodular.com/docs/guides/agent-export).

### mcp

Add the read-only `search` and `fetch` MCP endpoint at `<base>/mcp`. Requires `agentExport: true` and a request-time deployment adapter. The initializer's `mcpDeploy: "cloudflare"` option generates the Cloudflare adapter and deployment files; `mcpDeploy` is not a `zudoDoc` setting.

Default: `false`

See [MCP server](https://zudo-doc.takazudomodular.com/docs/guides/mcp-server).

### changelogs

Changelog generation config(s), or `false` to disable.

Default: `false`

For a multi-package project, add one entry per package. Each `sourceDir` must be the package-specific directory that contains non-index, per-release MDX files—not the top-level changelog landing directory:

```ts
changelogs: [
  {
    sourceDir: "src/content/docs/changelog/core",
    outputFile: "packages/core/CHANGELOG.md",
    packageName: "@acme/core",
  },
  {
    sourceDir: "src/content/docs/changelog/cli",
    outputFile: "packages/cli/CHANGELOG.md",
    packageName: "@acme/cli",
  },
],
```

See the [Changelog guide](https://zudo-doc.takazudomodular.com/docs/guides/changelog).

### claudeResources

Claude-resources ingestion config, or `false` to disable.

Type: `{ claudeDir; projectRoot?; scanRoot? } | false` · Default: `false`

```ts
claudeResources: { claudeDir: ".claude" },
```

See the [Claude Resources guide](https://zudo-doc.takazudomodular.com/docs/guides/claude-resources).

### codexResources

Codex-resources ingestion config, or `false` to disable.

Type: `{ codexDir; projectRoot?; scanRoot? } | false` · Default: `false`

```ts
codexResources: { codexDir: ".codex" },
```

See the [Codex Resources guide](https://zudo-doc.takazudomodular.com/docs/guides/codex-resources).

## Routing seams

### packageOwnedRoutes

Build-time package-owned route injection. When `true`, doc routes are injected from the package (no project-shipped `pages/*.tsx` layout stubs needed). Set `false` only if your project ships its own doc-route stubs.

Default: `true`

### chromeBindingsModule

Project-root-relative path to a host module exporting a `chromeBindings` object built with `defineChromeBindings` (from `@takazudo/zudo-doc/chrome-bindings`; only consumed when `packageOwnedRoutes` is on). Omit to keep the injected chrome shim at its package-default stubs.

Default: `undefined`

See [Host Chrome Bindings](https://zudo-doc.takazudomodular.com/docs/reference/host-chrome-bindings).

## Escape-hatch fields

These override non-serializable / data defaults. They travel the import graph rather than being JSON-serialized, so they can carry functions, Zod types, and component maps.

### buildDocsSchema

Replace the default docs-frontmatter Zod schema builder entirely. When omitted, `zudoDoc()` builds the package default (`@takazudo/zudo-doc/docs-schema`), governance-aware from `tagGovernance` + `tagVocabularyEntries`.

Type: `() => ZodType` · Default: `undefined` (package default used)

This is how you add custom frontmatter keys — see [Customizing → Rung 1](https://zudo-doc.takazudomodular.com/docs/reference/customizing#rung-1-%E2%80%94-custom-frontmatter-keys-builddocsschema).

### colorSchemes

Override the color-scheme palette map. When omitted, the two shipped schemes (Default Light / Default Dark) are used.

Type: `Record<string, ColorScheme>` · Default: `undefined`

### translations

Override the UI-string translation table. When omitted, the shipped en/ja/de defaults are used.

Type: `PresetTranslations` · Default: `undefined`

### directives

Override the directive → JSX-component-name map. When omitted, the canonical seven are used.

Type: `DirectiveVocabulary` · Default: `undefined`

[Dynamic content unavailable in text export]

`directives` replaces the vocabulary wholesale. Preserve the canonical directives when adding one, or existing `:::note`, `:::warning`, and other built-ins stop resolving.



```ts title="zfb.config.ts"
import { defaultDirectiveVocabulary } from "@takazudo/zudo-doc/directive-vocabulary-defaults";

export default defineConfig(
  zudoDoc({
    directives: {
      ...defaultDirectiveVocabulary,
      callout: "Callout",
    },
  }),
);
```

See the [Directives Registry](https://zudo-doc.takazudomodular.com/docs/markdown-features/directives-registry) for the complete component-registration recipe.

### tagVocabularyEntries

The tag-vocabulary **entries** array (distinct from the boolean `tagVocabulary` gate). Threaded into the route-context virtual module and consulted by the governance-aware default schema builder.

Type: `readonly PresetTagVocabularyEntry[]` · Default: `[]`

[Dynamic content unavailable in text export]

Do not conflate `tagVocabulary` (a `boolean` gate) with `tagVocabularyEntries` (the entries array). They are separate fields — the gate decides *whether* the vocabulary is consulted; the entries are *what* it consults.



## Shell passthrough fields

These are host-owned `ZfbConfig` shell fields, not part of `Settings`.

### port

Dev/preview server port.

Default: `4321`

### adapter

Deploy-target adapter package name (e.g. `"@takazudo/zfb-adapter-cloudflare"`). Omit for a pure static build.

Default: `undefined` (pure static build)

### bundle

zfb bundler options (`exclude` / `mainFields` / `external`), passed through verbatim when set.

Default: `undefined`

### wind

Top-level `WindConfig | false` override, passed to zfb rather than added to serialized `Settings`. Default: `undefined` (inherit the package preset).

The preset registers var-backed semantic tokens, `reset: "owned-v1"`, `dark: false`, the public `@takazudo/zudo-doc/wind.json` manifest, and the `sm`/`lg`/`xl` pixel breakpoints. It omits `spacingUnit`. zfb deep-merges an explicit override over those defaults; `wind: false` disables generation. Use ordinary `:root` declarations to change an existing token value; use `wind.tokens` when introducing a new utility name. See [Design System](https://zudo-doc.takazudomodular.com/docs/reference/design-system) for examples and audit coverage limits.

## An extended project as reference

This showcase's own `zfb.config.ts` is the canonical "extended project" example — it spreads a typed `settings` object and passes genuinely showcase-specific data blocks (tag vocabulary, custom translations) through the escape-hatch fields, then adds the shell fields (`port`, `adapter`, `bundle`):

```ts title="zfb.config.ts (this showcase)"
export default defineConfig(
  zudoDoc({
    ...settings,
    tagVocabularyEntries: tagVocabulary,
    translations,
    chromeBindingsModule: "./src/chrome-bindings.tsx",
    port: 4321,
    adapter: "@takazudo/zfb-adapter-cloudflare",
    bundle: { exclude: ["e2e/fixtures/**"] },
  }),
);
```

A minimal project needs none of that — just the handful of fields it changes.