Configuration
Every theme option lives under opts in your jsdoc.json (or, for TypeDoc, under the cleanJsdocTheme block in typedoc.json — see JSDoc vs TypeDoc just below). Setting up the rest of the build is covered in JSDoc Getting Started and TypeDoc Getting Started; this page documents the theme's own options.
Each option below shows the snippet for both tools in tabs — pick the one that matches your setup.
Unknown or misspelled options only warn by default (with a "did you mean?" hint) — the build continues. Set
strictto turn those warnings into errors.
JSDoc vs TypeDoc
Every option on this page is the same for both tools — only where you put it differs. In JSDoc the theme options go under opts; in TypeDoc, under cleanJsdocTheme.
Theme options live under opts, alongside JSDoc's own options:
{
source: { include: ["./src", "./README.md"] },
plugins: ["plugins/markdown"],
opts: {
destination: "dist",
recurse: true,
template: "node_modules/clean-jsdoc-theme/dist",
// ↓ theme options
siteName: "My Library",
sectionOrder: ["Getting Started", "Classes", "Modules"],
clubSidebarItems: true,
copyPage: { enabled: true, actions: ["copy", "view", "claude"] },
},
}The theme is loaded as a plugin and selected as an output; its options live under cleanJsdocTheme:
{
entryPoints: ["src/index.ts"],
plugin: ["@clean-jsdoc-theme/typedoc"],
outputs: [{ name: "clean-jsdoc-theme", path: "dist" }],
// ↓ theme options
cleanJsdocTheme: {
siteName: "My Library",
sectionOrder: ["Getting Started", "Classes", "Modules"],
clubSidebarItems: true,
copyPage: { enabled: true, actions: ["copy", "view", "claude"] },
},
}The option names and values are identical — only the namespace changes: opts (JSDoc) vs cleanJsdocTheme (TypeDoc). One exception: the outputSourceFiles and sourceLinkToComment options sit under JSDoc's templates.default and are JSDoc-only.
Site & identity
siteName
The title shown in the header — plain text, or a logo image.
Expected: a string, or a logo set object. The logo set keys are all strings: light and dark are the logo URLs (or local paths) used in each theme, default is the fallback when a theme-specific one isn't given, and alt is the text shown if the image fails to load (and read by screen readers).
opts: { siteName: "My Library" }
// or a logo that swaps with the theme:
opts: {
siteName: {
light: "./assets/logo.svg",
dark: "./assets/logo-dark.svg",
alt: "My Library",
},
}cleanJsdocTheme: { siteName: "My Library" }
// or a logo that swaps with the theme:
cleanJsdocTheme: {
siteName: {
light: "./assets/logo.svg",
dark: "./assets/logo-dark.svg",
alt: "My Library",
},
}Defaults to your package's name.
basePath
The site root path the renderer prefixes onto every internal link and asset — set it when the site is served under a sub-path rather than the domain root.
Expected: a string path. Defaults to "" (served at the root).
opts: { basePath: "/my-library" } // served at example.com/my-library/cleanJsdocTheme: { basePath: "/my-library" } // served at example.com/my-library/favicon
A path to a favicon image. The bridge copies it to a content-hashed _assets/ asset and emits a <link rel="icon"> (with a type derived from the extension — .svg → image/svg+xml) into every page's <head>.
Expected: a string file path (.svg, .png, .ico, …), relative to the working dir. Omitted → no favicon link.
opts: { favicon: "./assets/logo-small.svg" }cleanJsdocTheme: { favicon: "./assets/logo-small.svg" }An SVG favicon needs this option — browsers only auto-discover a root
favicon.ico, never an SVG. An SVG icon can also adapt to light/dark with a@media (prefers-color-scheme: dark)block inside the SVG.
Content sources
readme
A Markdown file rendered as the site home page.
Expected: a path string. (A root docs/index.md overrides it — see docs.)
opts: { readme: "./README.md" }cleanJsdocTheme: { readme: "./README.md" }docs
A directory of hand-written Markdown/HTML guides rendered as prose pages. The folder layout drives each page's URL and sidebar group; per-file YAML frontmatter (title, group, order, slug, hidden) overrides the defaults, and a root index.md becomes the home page.
Expected: a path string (a directory).
opts: { docs: "./docs" }cleanJsdocTheme: { docs: "./docs" }docGroups
The display order of the top-level doc groups in the sidebar.
Expected: an array of group-label strings.
opts: { docGroups: ["Getting Started", "Guides"] }cleanJsdocTheme: { docGroups: ["Getting Started", "Guides"] }This very site is built with the
docsanddocGroupsoptions — its guides are plain Markdown files grouped into the sidebar sections you're browsing right now. Want to build something similar? Browse the source: docs-site on GitHub.
defaultDocGroup
The group label a doc lands in when it declares none (no frontmatter group and no folder to humanize).
Expected: a single string.
opts: { defaultDocGroup: "Docs" }cleanJsdocTheme: { defaultDocGroup: "Docs" }tutorials
The JSDoc --tutorials directory. Each tutorial becomes a guide page, grouped under "Tutorials" in the sidebar.
Expected: a path string (a directory). Equivalent to JSDoc's -u flag.
opts: { tutorials: "./tutorials" }cleanJsdocTheme: { tutorials: "./tutorials" }Sidebar & navigation
sectionOrder
The order of all top-level sidebar sections — both your doc/category groups and the API kind labels (Classes, Modules, …). Listed labels come first in the given order; anything you omit is appended afterward.
Expected: an array of section-label strings.
opts: { sectionOrder: ["Getting Started", "Guides", "Classes", "Modules"] }cleanJsdocTheme: { sectionOrder: ["Getting Started", "Guides", "Classes", "Modules"] }clubSidebarItems
Collapse related entries (e.g. a module and its members) under a shared, collapsible parent, grouped by the path segment before the first /.
Expected: a boolean. Defaults to false.
opts: { clubSidebarItems: true }cleanJsdocTheme: { clubSidebarItems: true }menu
Custom links pinned above the sidebar navigation.
Expected: an array of entries. Each is an object with title, a link (or href), an optional id, and an optional icon — lucide:<name> or simpleicons:<name>, loaded from a CDN. Two optional fields control link presentation: target (the link target — an external link defaults to _blank) and class (extra CSS class(es) merged onto the rendered link).
opts: {
menu: [
{ title: "Home", link: "/", icon: "lucide:home" },
{ title: "GitHub", link: "https://github.com/you/repo", icon: "simpleicons:github" },
// Open in the same tab + tag with a custom class for styling via customCss:
{ title: "Changelog", link: "/changelog", target: "_self", class: "menu-changelog" },
],
}cleanJsdocTheme: {
menu: [
{ title: "Home", link: "/", icon: "lucide:home" },
{ title: "GitHub", link: "https://github.com/you/repo", icon: "simpleicons:github" },
{ title: "Changelog", link: "/changelog", target: "_self", class: "menu-changelog" },
],
}pageNav
The prev/next pager at the foot of each content page — two cards linking the adjacent pages in sidebar reading order. On by default; source-viewer pages never show it.
Expected: a boolean shorthand, or an object { enabled }. Defaults to true.
opts: { pageNav: false }
// or the object form: opts: { pageNav: { enabled: false } }cleanJsdocTheme: { pageNav: false }
// or the object form: cleanJsdocTheme: { pageNav: { enabled: false } }Appearance & assets
fonts
Override the type families.
Expected: an object with heading, body, and/or mono. heading and body are Google Font family names (loaded for you, existence-checked at build time); mono is a CSS font stack.
opts: {
fonts: { heading: "Fraunces", body: "Spline Sans", mono: "Spline Sans Mono" },
}cleanJsdocTheme: {
fonts: { heading: "Fraunces", body: "Spline Sans", mono: "Spline Sans Mono" },
}colors and darkColors
Recolor the theme. colors is the light-mode palette (also the :root default); darkColors is the dark-mode palette, emitted under [data-theme="dark"]. Each merges per key over the built-in palette, so you can override just bg and keep every other default.
Expected: an object with any subset of these keys, each a CSS color string (the theme ships oklch, but any valid CSS color works):
| Key | Role |
|---|---|
bg | Page background |
bgMuted | Subtle surfaces (code blocks, cards, sidebar) |
fg | Body text |
fgMuted | Secondary / muted text |
accent | Links, focus rings, primary buttons |
accentFg | Text/icon on an accent background |
border | Hairlines and dividers |
codeHeaderBg | Code-block header strip background |
codeHeaderFg | Code-block header label text |
codeHighlightBg | Highlighted code-line background (@playground / highlight=) |
The three code* keys style the code-block chrome — the header strip (its background + the CODE/filename label) and the tint on a highlighted line. They default to a neutral #f7f7f7-equivalent in light and elevated greys in dark, so you only set them to match a custom palette.
opts: {
colors: { bg: "oklch(0.99 0.01 95)", accent: "oklch(0.55 0.2 250)" },
darkColors: { bg: "oklch(0.18 0.01 250)", accent: "oklch(0.7 0.16 250)" },
}cleanJsdocTheme: {
colors: { bg: "oklch(0.99 0.01 95)", accent: "oklch(0.55 0.2 250)" },
darkColors: { bg: "oklch(0.18 0.01 250)", accent: "oklch(0.7 0.16 250)" },
}Unknown keys and non-string values are ignored. If you omit darkColors entirely, dark mode falls back to a sensible bg/fg swap of colors.
customCss and customJs
Inline CSS/JS injected into every page. Custom CSS loads after the theme stylesheet (so it overrides); custom JS runs last.
Expected: a string.
opts: { customCss: ".my-banner { color: rebeccapurple; }" }cleanJsdocTheme: { customCss: ".my-banner { color: rebeccapurple; }" }customCssFile and customJsFile
Like the above, but read from a file on disk. The bridge copies each to a content-hashed asset and links it.
Expected: a path string.
opts: { customCssFile: "./extra.css", customJsFile: "./extra.js" }cleanJsdocTheme: { customCssFile: "./extra.css", customJsFile: "./extra.js" }hashCustomAssets
Whether custom-asset filenames are content-hashed (for cache-busting). Set false to keep stable, unhashed names.
Expected: a boolean. Defaults to true.
opts: { hashCustomAssets: false }cleanJsdocTheme: { hashCustomAssets: false }footer
A custom site footer, rendered in place of the default footer on every page. Either an inline HTML string or { file: "./footer.html" } — the bridge reads the file at build time, so render() stays pure. It's trusted, author-controlled HTML (rendered verbatim, like v4's footer); style it with customCss / customCssFile, which load after the theme stylesheet so your selectors win.
Expected: a string, or { file }.
opts: { footer: "<div class='site-footer'>© 2026 My Library</div>" }
// or, for anything longer than a line:
opts: { footer: { file: "./footer.html" } }cleanJsdocTheme: { footer: "<div class='site-footer'>© 2026 My Library</div>" }
// or:
cleanJsdocTheme: { footer: { file: "./footer.html" } }meta
Site-wide custom <meta> tags. Pass an array of attribute objects — each object's key/value pairs become the attributes of one <meta> tag in <head> (name/content, property/content, http-equiv, charset, …). The theme emits charset/viewport/an auto description itself and de-dupes: an author entry sharing an identifying attribute (name / property / http-equiv / charset) replaces the theme's default rather than duplicating it (so your description wins). Values are escaped automatically; invalid attribute names are dropped. These are site-wide (no per-page meta yet).
Expected: an array of { [attr]: value } objects.
opts: {
meta: [
{ name: "description", content: "Fast, typed docs for My Library" },
{ name: "keywords", content: "jsdoc, typescript, docs" },
{ property: "og:title", content: "My Library" },
{ property: "og:image", content: "https://example.com/card.png" },
{ name: "theme-color", content: "#0b0b0b" }
]
}cleanJsdocTheme: {
meta: [
{ name: "description", content: "Fast, typed docs for My Library" },
{ property: "og:title", content: "My Library" }
]
}LLM & copy page
copyPage
The per-page "copy page" / "open in LLM" button (content pages only).
Expected: a boolean shorthand, or an object { enabled, actions } where actions is any of copy, view, claude, chatgpt, perplexity. On by default with all actions.
opts: { copyPage: { enabled: true, actions: ["copy", "view", "claude"] } }
// or simply: opts: { copyPage: false }cleanJsdocTheme: { copyPage: { enabled: true, actions: ["copy", "view", "claude"] } }
// or simply: cleanJsdocTheme: { copyPage: false }aiPrompt
A custom instruction prepended when a page is handed to an LLM via the open-in actions.
Expected: a string.
opts: { aiPrompt: "You are helping a developer use My Library. " }cleanJsdocTheme: { aiPrompt: "You are helping a developer use My Library. " }Source files
These two are JSDoc-only. They live under
templates.default(JSDoc's default-template namespace), not underoptsorcleanJsdocTheme.
outputSourceFiles
Whether to generate the syntax-highlighted source-file viewer pages and the Source: file:line links on members.
Expected: a boolean. Defaults to true; set false to suppress both.
templates: { default: { outputSourceFiles: false } }sourceLinkToComment
Where a Source: link lands: the symbol's declaration (default) or its documentation comment.
Expected: a boolean. Defaults to false (land on the declaration).
templates: { default: { sourceLinkToComment: true } }How assets are handled
You don't configure this, but it's worth knowing how local files referenced from your docs and README are processed. Any image you link with a relative or root-relative path —  — is copied into the site's _assets/ directory under a content-hashed name (e.g. _assets/flow.3de65053.svg) and the reference is rewritten to point at it. The hash is derived from the file's bytes, so an unchanged file keeps a stable, cacheable URL across builds and a changed one cache-busts automatically. External (https://…) and data: URLs are left untouched.
.svg files get one extra step: their markup is inlined directly into the page rather than loaded through an <img>. That lets an SVG's own [data-theme="dark"] styles follow the in-page theme toggle — an <img>-loaded SVG can only see the operating system's color scheme, never your site's toggle.
Logos (siteName) and customCssFile / customJsFile ride the same content-hashed _assets/ pipeline.
Localization
These two opt your site into a multi-language build — one static site per locale, with a header language switcher and hreflang alternates wired in. They declare the locales; the actual translation workflow (extracting catalogs, translating, and building each locale) runs through the clean-jsdoc CLI. The full walkthrough is in Localize your docs. A build with no locales is unaffected — it renders exactly as before.
Localized builds are JSDoc-only today. The TypeDoc bridge can extract catalogs but does not yet render the per-locale sites.
locales
The languages to build.
Expected: an array of { code, name } objects (the name is the switcher label), or bare locale-code strings. Codes are BCP-47-ish (en, pt-BR).
opts: {
locales: [
{ code: "en", name: "English" },
{ code: "ja", name: "日本語" },
],
}cleanJsdocTheme: {
locales: [
{ code: "en", name: "English" },
{ code: "ja", name: "日本語" },
],
}defaultLocale
The language rendered at the site root (every other locale lands under /<code>).
Expected: a locale code that appears in locales. Optional — defaults to the first entry.
opts: { defaultLocale: "en" }cleanJsdocTheme: { defaultLocale: "en" }Build
strict
Escalate option diagnostics (a bad font name, an unknown key) from warnings to hard build errors.
Expected: a boolean. Defaults to false.
opts: { strict: true }cleanJsdocTheme: { strict: true }progress
Toggle the build's progress output (the per-stage spinners).
Expected: a boolean. Defaults to true.
opts: { progress: false }cleanJsdocTheme: { progress: false }