-
Notifications
You must be signed in to change notification settings - Fork 45
guides faq
Short, practical answers to the questions that come up most — embedding live content, writing richer doc comments, and a few common configuration tweaks. Each recipe links to the page with the full details.
The theme can drop a sandboxed <iframe> into any page — a CodePen, a YouTube
video, a StackBlitz, or any site. There are two ways to author one, and they
share one config grammar:
-
In prose (README, guides, the
docsfolder) — an```iframefenced block. -
In a doc comment — the
@iframe <url> key=valueblock tag.
The first token is the URL; the rest are key=value options — title, height
(px), aspectRatio (e.g. 16/9), width, allow, sandbox, clickToLoad,
and themed. Only https:// (or protocol-relative //) URLs are accepted.
Use the pen's embed URL (https://codepen.io/USER/embed/PEN_ID):
```iframe
https://codepen.io/USER/embed/PEN_ID title="CodePen demo" height=400
```Tip
On CodePen, open Embed and copy the URL from the <iframe src="…">
snippet. Add clickToLoad=true to show a light poster until the reader clicks.
@iframe embeds an existing pen by URL. To open the code from an
@example — prefilled, no pen needed — use the playground feature instead:
turn it on in opts.playground, then tag the example with @playground:
/**
* @example
* const out = resize(img, 200);
* @playground codepen jsfiddle filename=resize.js highlight=1
*/
export function resize(img, width) {}The code block's header gains an "Open Code in" dropdown (CodePen / JSFiddle /
CodeSandbox), all client-side — no API key. It also works in prose (a
```js playground fence or a <playground> block). Full walkthrough:
Add a playground. (@playground needs
tags.allowUnknownTags: true, same as @iframe.)
Use the embed URL (https://www.youtube.com/embed/VIDEO_ID) and give it a
16/9 aspect ratio instead of a fixed height:
```iframe
https://www.youtube.com/embed/VIDEO_ID title="Intro video" aspectRatio=16/9
```Any https:// URL works — set a height in pixels or an aspectRatio:
```iframe
https://example.com title="Live preview" height=480
```Use the @iframe block tag — it renders after the symbol's @example:
/**
* Renders the chart.
*
* @iframe https://codepen.io/USER/embed/PEN_ID title="Demo" height=400
*/
export function render() {}Important
@iframe is an unknown tag to base JSDoc — set
tags.allowUnknownTags: true in your jsdoc.json, or JSDoc strips it before
the theme runs. (TypeDoc needs no such flag.)
- The URL must start with
https://(or//). Plainhttp://or a relative path is rejected, and the embed is silently dropped. - Unknown option keys are ignored with a build warning — check spelling against the list above.
Add clickToLoad=true: the reader sees a poster button (with a <noscript>
fallback) and the iframe loads on click. By default it loads immediately and
works with no JavaScript at all.
By default, yes — the embed URL is re-resolved when the theme changes (a
{theme} token is swapped, or ?theme-id=<theme> is appended). Opt out with
themed=false. Full reference: Embeds & live demos.
Everything the theme does in prose also works inside your JSDoc / TypeDoc descriptions — they flow through the same converter.
Write a GitHub-style alert blockquote right in the description:
/**
* Connects to the database.
*
* > [!WARNING]
* > Call `close()` when you're done — connections are not pooled.
*/
export function connect() {}The markers map to four styles: [!NOTE] / [!INFO] / [!IMPORTANT] → info,
[!TIP] / [!SUCCESS] → tip, [!WARNING] / [!CAUTION] → warning, and
[!ERROR] / [!DANGER] → error. See Callouts.
Yes — the same <steps> (and <tabs>) markup works in a description; there's no
dedicated tag, you write the markup directly:
/**
* @module my-api
*
* <steps>
*
* <step label="Install">
*
* ```sh
* npm install my-api
* ```
*
* </step>
*
* <step label="Use">
*
* ```js
* import { go } from 'my-api';
* ```
*
* </step>
*
* </steps>
*/See Steps and Tabs for the full syntax and the blank-line rule, and the live sample-api module page for it rendered.
Just use @deprecated — the theme renders it as a callout automatically, no
marker needed:
/**
* @deprecated Use {@link connect} instead.
*/Set siteName to a logo object with light / dark image paths (and an alt
fallback) — see siteName.
Use @category / @order on symbols, frontmatter group / order on guide
pages, and the sectionOrder option. Structure your
sidebar covers every lever.
Note
Those levers control the JSDoc API sidebar. For TypeDoc, the API
sidebar is a module/folder hierarchy — only doc groups and menu respond to
the options above. See
TypeDoc flavor.
The most likely cause: tags.allowUnknownTags isn't true in your
jsdoc.json. These are all tags base JSDoc doesn't define, so it strips them
before the theme runs — your categories collapse to the default kind sections,
@order does nothing, and @playground / @iframe never render. Set the flag:
{ "tags": { "allowUnknownTags": true } }(TypeDoc has no such restriction — it passes these through.) See Custom tags for the full list.
Point opts.docs at a folder of Markdown. See Build a guides
site and Combine guides +
API.
Set footer to a string of HTML — it renders at
the bottom of every page, in place of the default footer:
opts: {
template: "./node_modules/clean-jsdoc-theme",
footer: "<div class='site-footer'>© 2026 My Library — built with care</div>"
}For anything longer than a line, hand it a file instead — it's read at build time and keeps your config tidy:
opts: { footer: { file: "./footer.html" } }Style it with your own CSS. The footer carries whatever classes you put in
your markup; style them with customCss /
customCssFile, which the theme
loads after its own stylesheet, so your rules win without !important:
opts: { footer: { file: "./footer.html" }, customCssFile: "./footer.css" }/* footer.css */
.site-footer { padding: 2rem 0; text-align: center; color: var(--clean-text-muted); }
.site-footer a { color: var(--clean-primary); }Need behaviour (e.g. a dynamic year)? Add customJs / customJsFile — it runs
last, after the theme's own scripts. Most footers don't need it.
Set meta to an array of attribute objects. Each
object becomes one <meta> tag in every page's <head> — the keys are the
attributes:
opts: {
meta: [
{ name: "description", content: "Fast, typed docs for My Library" },
{ name: "keywords", content: "jsdoc, typescript, documentation" },
{ name: "theme-color", content: "#0b0b0b" }
]
}Open Graph and Twitter cards work the same way — they're just <meta> tags:
opts: {
meta: [
{ property: "og:title", content: "My Library" },
{ property: "og:image", content: "https://example.com/social-card.png" },
{ name: "twitter:card", content: "summary_large_image" }
]
}A few things worth knowing:
-
charsetandviewportare already emitted — you don't need to add them. -
A
generatortag is added —<meta name="generator" content="clean-jsdoc-theme <version>">records which theme version built the site. Pass your own{ name: "generator", … }to replace it. -
Your tag wins over the theme's default. A
{ name: "description", … }replaces the auto description; you won't get two. - These are site-wide — the same tags render on every page (per-page social cards aren't supported yet).
- Values are escaped automatically, so quotes and angle brackets are safe.
Point favicon at an image file. The theme copies
it to a content-hashed asset and adds a <link rel="icon"> to every page's
<head>:
opts: { favicon: "./assets/logo.svg" }This is the way to use an SVG favicon — browsers only auto-discover a root
favicon.ico, never an SVG, so it needs the <link> the theme emits. (An SVG
can even adapt to light/dark via a @media (prefers-color-scheme: dark) block
inside it.) The v4 favicon option was briefly dropped early in v5 and is back.
Configure or disable it with copyPage.
Unknown or misspelled options warn by default (with a "did you mean?" hint)
and the build continues. Set strict to turn
those warnings into errors.
My dist is empty and every page fails with "Cannot read properties of undefined (reading 'context')"
Upgrade to 5.0.8 or newer. Earlier versions declared preact as a plain
dependency in the theme's internal packages, so under Yarn Berry / Plug'n'Play
the server renderer and the components could bind to two different Preact
instances — which made every page fail to render and left dist empty. 5.0.8
declares preact as a peer dependency so a single instance is always shared.
If you can't upgrade yet, switch that project off PnP by adding to .yarnrc.yml:
nodeLinker: node-modulesthen re-run yarn install.
Declare your languages in the same opts block and drive the build with the
clean-jsdoc CLI (the @clean-jsdoc-theme/aadesh package):
{
"opts": {
"locales": [
{ "code": "en", "name": "English" },
{ "code": "ja", "name": "日本語" }
],
"defaultLocale": "en"
}
}npm i -D @clean-jsdoc-theme/aadesh
clean-jsdoc i18n extract # build the per-locale translation catalogs
# …translate the JSON (or `clean-jsdoc i18n prompt` for an LLM prompt)…
clean-jsdoc build # render one static site per localeYou get one site per language (the default at the root, others under
/<locale>), a header language switcher, and hreflang tags. Prose is localized
by file — a README.<locale>.md home page and a docs.<locale>/ overlay — and
fonts per locale via "ja:heading"-style keys. The full walkthrough is in
Localize your docs.
The localization commands now live under an i18n group, so the CLI has room to
grow beyond localization. Update your commands (and any package.json scripts):
| Old | New |
|---|---|
clean-jsdoc extract |
clean-jsdoc i18n extract |
clean-jsdoc prompt |
clean-jsdoc i18n prompt |
clean-jsdoc validate |
clean-jsdoc i18n validate |
clean-jsdoc build |
clean-jsdoc build (unchanged) |
build stays top-level because it renders your site whether or not you use
multiple locales — the per-locale fan-out is just what build does when
opts.locales is set. All flags are unchanged. Run clean-jsdoc with no
arguments for the interactive menu, which now groups the i18n steps together.
clean-jsdoc-theme is built to be LLM-friendly, in two directions:
-
Your generated site is readable by AI. Every page emits a companion
Markdown file (
<page>/index.md), and a per-page copy / open-in-LLM button hands the clean.mdstraight to Claude / ChatGPT / Perplexity — so an assistant reads the exact same reference your users do. Tune the handoff withcopyPageandaiPrompt. -
Setting up the theme is itself AI-assisted. There's a downloadable
skill that turns any coding assistant into a
clean-jsdoc-theme expert — point your agent at it and it can write your
jsdoc.json/typedoc.json, author guides with callouts/steps/tabs, structure the sidebar, wire up cross-links, set up localization, and debug a build, all from source-verified knowledge instead of guesswork. Drop the skill folder into your assistant (e.g..claude/skills/) and just ask for what you want.
So whether the LLM is reading your docs or building them, you don't have
to do the translating — point it at the companion .md and the skill.
This wiki is auto-generated from docs-site/docs. Edit there — changes sync automatically. Full docs: https://ankdev.me/clean-jsdoc-theme/