{"$schema":"https://doc-kit.nodejs.org/schemas/api-doc/1.0.1.json","id":"customization","path":"/customization","type":"module","module":"customization","title":"Customizing the site","introducedIn":null,"sourceLink":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"The site the `html` generator builds starts out neutral: your project's name\nin place of a logo, a grey accent color, and a sidebar listing every page. This\nguide makes it yours, from colors and a logo to components of your own and whole\nnew page layouts, starting from the site you built in\n[Getting started](./getting-started.html).\n\nMost of this goes in the `html` section of your configuration file:\n\n```js displayName=\"doc-kit.config.mjs\"\nimport { join } from 'node:path';\n\nexport default {\n  target: ['html', 'orama-db'],\n\n  global: {\n    project: 'Kittens',\n    version: '1.0.0',\n    input: ['docs/**/*.{md,mdx}'],\n    output: 'out',\n  },\n\n  html: {\n    title: '{project} docs',\n    stylesheets: [join(import.meta.dirname, 'theme/styles.css')],\n    imports: {\n      '#theme/Logo': join(import.meta.dirname, 'theme/Logo.jsx'),\n    },\n  },\n};\n```\n\nRelative paths in the `html` section resolve against the directory you run\ndoc-kit from, so the examples build absolute ones from the configuration\nfile's location with `import.meta.dirname`. The\n[Website reference](./generators/html.html) lists every option.","summary":"The site the `html` generator builds starts out neutral: your project's name in place of a logo, a grey accent color, and a sidebar listing every page. This guide makes it yours, from colors and a logo to components of your own and whole new page layouts, starting from the site you built in Getting started.","examples":[{"language":"js","displayName":"doc-kit.config.mjs","code":"import { join } from 'node:path';\n\nexport default {\n  target: ['html', 'orama-db'],\n\n  global: {\n    project: 'Kittens',\n    version: '1.0.0',\n    input: ['docs/**/*.{md,mdx}'],\n    output: 'out',\n  },\n\n  html: {\n    title: '{project} docs',\n    stylesheets: [join(import.meta.dirname, 'theme/styles.css')],\n    imports: {\n      '#theme/Logo': join(import.meta.dirname, 'theme/Logo.jsx'),\n    },\n  },\n};"}],"children":[{"kind":"section","id":"name-and-title","name":"Name and title","title":"Name and title","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"`project` names the site: the default logo shows it, and so does the version\nselector. Set it under `global`, since other generators use it too, or under\n`html` for the site alone. Without one, doc-kit uses the `name` in your\n`package.json`.\n\n`title` is the template for the second half of each page's `<title>`. The first\nhalf is the page's own title, from its `title` frontmatter or its first\nheading:\n\n```js displayName=\"doc-kit.config.mjs\"\nexport default {\n  html: {\n    // \"Getting started | Kittens docs\"\n    title: '{project} docs',\n  },\n};\n```\n\nAny option of the `html` section, global ones included, works as a\nplaceholder, such as `{project}`, or `{version}` for the version number. The\ndefault is `'{project} {version} Documentation'`, so set `global.version` or\ndrop `{version}`: without one, it shows the version of Node.js running\ndoc-kit. An empty `title` leaves each page its own title alone.","summary":"`project` names the site: the default logo shows it, and so does the version selector. Set it under `global`, since other generators use it too, or under `html` for the site alone. Without one, doc-kit uses the `name` in your `package.json`.","examples":[{"language":"js","displayName":"doc-kit.config.mjs","code":"export default {\n  html: {\n    // \"Getting started | Kittens docs\"\n    title: '{project} docs',\n  },\n};"}],"children":[]},{"kind":"section","id":"colors-and-fonts","name":"Colors and fonts","title":"Colors and fonts","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"Restyle the site with a stylesheet of your own. `stylesheets` lists CSS files\nthat are bundled after doc-kit's own, so their rules and custom properties\nwin, and that go through the bundler like any other CSS (`@import` and `url()`\nwork).\n\nThe UI takes its accent color, used for links, focus rings and active states,\nfrom nine `--color-brand-*` custom properties. Redefine them all to rebrand the\nsite:\n\n```css displayName=\"theme/styles.css\"\n:root {\n  --color-brand-100: #f3f0ff;\n  --color-brand-200: #e4dcff;\n  --color-brand-300: #cbbcff;\n  --color-brand-400: #ac94fa;\n  --color-brand-500: #8e6cf0;\n  --color-brand-600: #7350d9;\n  --color-brand-700: #5c3db8;\n  --color-brand-800: #482f91;\n  --color-brand-900: #33216a;\n}\n```\n\n```js displayName=\"doc-kit.config.mjs\"\nimport { join } from 'node:path';\n\nexport default {\n  html: {\n    stylesheets: [join(import.meta.dirname, 'theme/styles.css')],\n  },\n};\n```\n\nOne palette serves both color schemes: the UI picks lighter shades in dark\nmode. For styles of your own that differ in dark mode, use the\n`[data-theme='dark']` selector, which matches once the `<html>` element is in\ndark mode.\n\nThe text uses the font in `--font-open-sans`, and code the one in\n`--font-ibm-plex-mono`. They are named after doc-kit's default fonts, but hold\nwhatever font stack you give them:\n\n```css displayName=\"theme/styles.css\"\n@font-face {\n  font-family: 'Inter Variable';\n  font-style: normal;\n  font-weight: 100 900;\n  font-display: swap;\n  src: url('./fonts/inter-latin-wght-normal.woff2') format('woff2');\n}\n\n:root {\n  --font-open-sans: 'Inter Variable', system-ui, sans-serif;\n}\n```\n\nPrefer a stylesheet to a `<style>` tag in [the `<head>`](#the-head): it is\nbundled, minified and cached with the rest of the site's CSS.\n\n> \\[!TIP]\n> The default template preloads doc-kit's own fonts through `${preloads}`. If\n> you replace them, [a template of your own](#the-page-template) can drop or\n> filter `${preloads}`; this site's template keeps only the monospace one.","summary":"Restyle the site with a stylesheet of your own. `stylesheets` lists CSS files that are bundled after doc-kit's own, so their rules and custom properties win, and that go through the bundler like any other CSS (`@import` and `url()` work).","examples":[{"language":"css","displayName":"theme/styles.css","code":":root {\n  --color-brand-100: #f3f0ff;\n  --color-brand-200: #e4dcff;\n  --color-brand-300: #cbbcff;\n  --color-brand-400: #ac94fa;\n  --color-brand-500: #8e6cf0;\n  --color-brand-600: #7350d9;\n  --color-brand-700: #5c3db8;\n  --color-brand-800: #482f91;\n  --color-brand-900: #33216a;\n}"},{"language":"js","displayName":"doc-kit.config.mjs","code":"import { join } from 'node:path';\n\nexport default {\n  html: {\n    stylesheets: [join(import.meta.dirname, 'theme/styles.css')],\n  },\n};"},{"language":"css","displayName":"theme/styles.css","code":"@font-face {\n  font-family: 'Inter Variable';\n  font-style: normal;\n  font-weight: 100 900;\n  font-display: swap;\n  src: url('./fonts/inter-latin-wght-normal.woff2') format('woff2');\n}\n\n:root {\n  --font-open-sans: 'Inter Variable', system-ui, sans-serif;\n}"}],"children":[]},{"kind":"section","id":"logo","name":"Logo","title":"Logo","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"The logo sits at the start of the navigation bar, inside a link to the home\npage. By default it is the project's name as text. To replace it, point the\n`#theme/Logo` alias at a component of your own:\n\n```js displayName=\"doc-kit.config.mjs\"\nimport { join } from 'node:path';\n\nexport default {\n  html: {\n    imports: {\n      '#theme/Logo': join(import.meta.dirname, 'theme/Logo.jsx'),\n    },\n  },\n};\n```\n\n```jsx displayName=\"theme/Logo.jsx\"\nimport { project } from '#theme/config';\n\nexport default () => (\n  <span class=\"logo\">\n    <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <circle cx=\"12\" cy=\"12\" r=\"12\" fill=\"var(--color-brand-600)\" />\n    </svg>\n    {project}\n  </span>\n);\n```\n\nThe logo is one of the [parts of the page](#replacing-parts-of-the-page) you\ncan replace, and `#theme/config` holds the\n[build-time data](#the-themes-data) components can read.\n\n> \\[!NOTE]\n> Theme components are [Preact](https://preactjs.com/) components, so\n> `preact` has to resolve from your project, for their JSX and any hooks they\n> import. npm usually hoists it from `@doc-kit/generator-react`; with pnpm,\n> install it yourself.","summary":"The logo sits at the start of the navigation bar, inside a link to the home page. By default it is the project's name as text. To replace it, point the `#theme/Logo` alias at a component of your own:","examples":[{"language":"js","displayName":"doc-kit.config.mjs","code":"import { join } from 'node:path';\n\nexport default {\n  html: {\n    imports: {\n      '#theme/Logo': join(import.meta.dirname, 'theme/Logo.jsx'),\n    },\n  },\n};"},{"language":"jsx","displayName":"theme/Logo.jsx","code":"import { project } from '#theme/config';\n\nexport default () => (\n  <span class=\"logo\">\n    <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      <circle cx=\"12\" cy=\"12\" r=\"12\" fill=\"var(--color-brand-600)\" />\n    </svg>\n    {project}\n  </span>\n);"}],"children":[]},{"kind":"section","id":"the-head","name":"The <head>","title":"The `<head>`","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"`head` adds tags to every page's `<head>`:\n\n```js displayName=\"doc-kit.config.mjs\"\nexport default {\n  html: {\n    head: {\n      meta: [\n        { name: 'description', content: 'Kittens: an API for cats.' },\n        { property: 'og:image', content: 'https://kittens.example/og.png' },\n      ],\n      links: [{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' }],\n      html: ['<script defer src=\"https://stats.example/script.js\"></script>'],\n    },\n\n    // Copies what is in `public/` to the root of the output\n    pathsToCopy: [{ public: '.' }],\n  },\n};\n```\n\n`meta` and `links` take one object of attributes per tag, and `html` takes raw\nmarkup, added as written. The tags are the same on every page, so link files\nby absolute path or full URL.\n\nFiles you link to have to be in the output. `pathsToCopy` copies them there:\nby default the `assets`, `public` and `static` directories of the working\ndirectory, under the same names. An object maps a source to a destination\ninside the output instead, as `{ public: '.' }` does above to put\n`favicon.svg` at the root. Setting `pathsToCopy` replaces the default list.","summary":"`head` adds tags to every page's `<head>`:","examples":[{"language":"js","displayName":"doc-kit.config.mjs","code":"export default {\n  html: {\n    head: {\n      meta: [\n        { name: 'description', content: 'Kittens: an API for cats.' },\n        { property: 'og:image', content: 'https://kittens.example/og.png' },\n      ],\n      links: [{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' }],\n      html: ['<script defer src=\"https://stats.example/script.js\"></script>'],\n    },\n\n    // Copies what is in `public/` to the root of the output\n    pathsToCopy: [{ public: '.' }],\n  },\n};"}],"children":[]},{"kind":"section","id":"the-page-template","name":"The page template","title":"The page template","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"Every page is written from an HTML template. It places the `head` tags and also\nwrites the `<title>`, the `og:title` and `og:type` tags, and the tags loading\nthe site's scripts and styles. For anything else, copy the default template\n(`src/html/template.html` in `@doc-kit/generator-react`) and point\n`templatePath` at your copy. Keep `${assets}` in its `<head>`, or the pages\nload no scripts or styles. The\n[Website reference](./generators/html.html#html-template) lists the template's\nvariables.","summary":"Every page is written from an HTML template. It places the `head` tags and also writes the `<title>`, the `og:title` and `og:type` tags, and the tags loading the site's scripts and styles. For anything else, copy the default template (`src/html/template.html` in `@doc-kit/generator-react`) and point `templatePath` at your copy. Keep `${assets}` in its `<head>`, or the pages load no scripts or styles. The Website reference lists the template's variables.","examples":[],"children":[]},{"kind":"section","id":"navigation","name":"Navigation","title":"Navigation","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"Without configuration, the sidebar has one group, `API Documentation`, listing\nevery page by its title (alphabetically, with `index` first), and the\nnavigation bar has no links. `navigation` replaces either:\n\n```js displayName=\"doc-kit.config.mjs\"\nexport default {\n  html: {\n    navigation: {\n      sidebar: [\n        {\n          groupName: 'Guides',\n          items: [\n            { label: 'Getting started', link: '/getting-started' },\n            {\n              label: 'Recipes',\n              link: '/recipes',\n              items: [\n                { label: 'Overview', link: '/recipes' },\n                { label: 'Feeding', link: '/recipes/feeding' },\n              ],\n            },\n          ],\n        },\n        {\n          groupName: 'Reference',\n          items: [{ label: 'The `Kitten` class', link: '/kitten' }],\n        },\n      ],\n      navbar: [\n        { text: 'Guide', link: '/getting-started' },\n        {\n          text: 'GitHub',\n          link: 'https://github.com/example/kittens',\n          target: '_blank',\n        },\n      ],\n      showCrossLinks: true,\n    },\n  },\n};\n```\n\nA sidebar `link` is a page's path without its extension, such as\n`/getting-started`, and works wherever the site is served from. An item with\nnested `items` opens and closes rather than linking anywhere, so list its own\npage as its first nested item, as `Recipes` does. `showCrossLinks` ends each\npage with links to the previous and next top-level items of your `sidebar`.\nThe [Website reference](./generators/html.html#navigation) lists every field.\n\nThe navigation bar also holds the search box, the theme switch, and a link to\nthe GitHub repository when `global.repository` is set. The search box appears\nwhen you build the [search index](./generators/orama-db.html) too; set\n`showSearchBox` to `true` or `false` to decide yourself. The sidebar adds a\nversion selector when your configuration has a `changelog`, as\n[Publishing several versions](./publishing.html#publishing-several-versions)\ndescribes.","summary":"Without configuration, the sidebar has one group, `API Documentation`, listing every page by its title (alphabetically, with `index` first), and the navigation bar has no links. `navigation` replaces either:","examples":[{"language":"js","displayName":"doc-kit.config.mjs","code":"export default {\n  html: {\n    navigation: {\n      sidebar: [\n        {\n          groupName: 'Guides',\n          items: [\n            { label: 'Getting started', link: '/getting-started' },\n            {\n              label: 'Recipes',\n              link: '/recipes',\n              items: [\n                { label: 'Overview', link: '/recipes' },\n                { label: 'Feeding', link: '/recipes/feeding' },\n              ],\n            },\n          ],\n        },\n        {\n          groupName: 'Reference',\n          items: [{ label: 'The `Kitten` class', link: '/kitten' }],\n        },\n      ],\n      navbar: [\n        { text: 'Guide', link: '/getting-started' },\n        {\n          text: 'GitHub',\n          link: 'https://github.com/example/kittens',\n          target: '_blank',\n        },\n      ],\n      showCrossLinks: true,\n    },\n  },\n};"}],"children":[]},{"kind":"section","id":"replacing-parts-of-the-page","name":"Replacing parts of the page","title":"Replacing parts of the page","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"Every part of the page around your content is a component, imported through a\n`#theme/*` alias:\n\n![The parts of the html generator's page, each an import alias you can swap](./diagrams/theme-slots.svg)\n\n| Alias               | What it renders                                 | Default                                              |\n| ------------------- | ----------------------------------------------- | ---------------------------------------------------- |\n| `#theme/Layout`     | The whole page, around the content              | The parts below, around the content                  |\n| `#theme/Logo`       | The logo, inside the navigation bar's home link | The project's name                                   |\n| `#theme/Navigation` | The navigation bar                              | Links, search box, theme switch, GitHub link         |\n| `#theme/Sidebar`    | The sidebar                                     | Page links and the version selector                  |\n| `#theme/Metabar`    | The column beside the content                   | Table of contents, reading time, view and edit links |\n| `#theme/Footer`     | The bottom of every page                        | Nothing                                              |\n\nPoint an alias at your own component in `imports`, and every page uses it. The\naliases you leave out keep their defaults:\n\n```js displayName=\"doc-kit.config.mjs\"\nimport { join } from 'node:path';\n\nexport default {\n  html: {\n    imports: {\n      '#theme/Footer': join(import.meta.dirname, 'theme/Footer.jsx'),\n    },\n  },\n};\n```\n\n```jsx displayName=\"theme/Footer.jsx\"\nimport { project, repository } from '#theme/config';\n\nexport default () => (\n  <footer class=\"footer\">\n    {project} is released under the{' '}\n    <a href={`https://github.com/${repository}/blob/main/LICENSE`}>\n      MIT License\n    </a>\n    .\n  </footer>\n);\n```\n\nYour component receives the props of the one it replaces: the navigation bar,\nthe sidebar and the metabar get the page's `metadata`, and the metabar also\ngets `headings` and `readingTime`. The logo and the footer get none.\n\nTo build on a default rather than start over, import it from\n`@doc-kit/generator-react/html/ui/components/`: `NavBar.jsx`,\n`SideBar/index.jsx`, `MetaBar/index.jsx` or `Layout/index.jsx`. Import the\nother parts through their aliases, so your overrides still apply.","summary":"Every part of the page around your content is a component, imported through a `#theme/*` alias:","examples":[{"language":"js","displayName":"doc-kit.config.mjs","code":"import { join } from 'node:path';\n\nexport default {\n  html: {\n    imports: {\n      '#theme/Footer': join(import.meta.dirname, 'theme/Footer.jsx'),\n    },\n  },\n};"},{"language":"jsx","displayName":"theme/Footer.jsx","code":"import { project, repository } from '#theme/config';\n\nexport default () => (\n  <footer class=\"footer\">\n    {project} is released under the{' '}\n    <a href={`https://github.com/${repository}/blob/main/LICENSE`}>\n      MIT License\n    </a>\n    .\n  </footer>\n);"}],"children":[{"kind":"section","id":"the-themes-data","name":"The theme's data","title":"The theme's data","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"Components read build-time data from the `#theme/config` module. Import only\nwhat you need:\n\n```jsx displayName=\"theme/Footer.jsx\"\nimport { project, version } from '#theme/config';\n\nexport default () => (\n  <footer class=\"footer\">\n    {project} v{version.version}\n  </footer>\n);\n```\n\n`version` is the version being built, and `version.version` its number. The\nmodule also exports `repository`, `baseURL`, the list of `pages`, and most\nother `html` options; the\n[Website reference](./generators/html.html#theme-config-module) lists them all.\n\nFor data of your own, add modules with `virtualImports`. Each key is an import\nspecifier, and each value the module's source code:\n\n```js displayName=\"doc-kit.config.mjs\"\nexport default {\n  html: {\n    virtualImports: {\n      '#theme/links': 'export const chat = \"https://chat.example/kittens\";',\n    },\n  },\n};\n```\n\n```jsx displayName=\"theme/Footer.jsx\"\nimport { chat } from '#theme/links';\n\nexport default () => (\n  <footer class=\"footer\">\n    Questions? <a href={chat}>Ask in the chat</a>.\n  </footer>\n);\n```","summary":"Components read build-time data from the `#theme/config` module. Import only what you need:","examples":[{"language":"jsx","displayName":"theme/Footer.jsx","code":"import { project, version } from '#theme/config';\n\nexport default () => (\n  <footer class=\"footer\">\n    {project} v{version.version}\n  </footer>\n);"},{"language":"js","displayName":"doc-kit.config.mjs","code":"export default {\n  html: {\n    virtualImports: {\n      '#theme/links': 'export const chat = \"https://chat.example/kittens\";',\n    },\n  },\n};"},{"language":"jsx","displayName":"theme/Footer.jsx","code":"import { chat } from '#theme/links';\n\nexport default () => (\n  <footer class=\"footer\">\n    Questions? <a href={chat}>Ask in the chat</a>.\n  </footer>\n);"}],"children":[]}]},{"kind":"section","id":"custom-components-and-mdx","name":"Custom components and MDX","title":"Custom components and MDX","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"Markdown pages have no components: their tags are plain HTML. To use\ncomponents in a page, write it in [MDX](./writing-docs.html#mdx-pages), register\nyour components in `components`, and use them as tags:\n\n```js displayName=\"doc-kit.config.mjs\"\nimport { join } from 'node:path';\n\nexport default {\n  html: {\n    components: {\n      Hero: '#theme/Hero',\n    },\n    imports: {\n      '#theme/Hero': join(import.meta.dirname, 'theme/Hero.jsx'),\n    },\n  },\n};\n```\n\n```jsx displayName=\"theme/Hero.jsx\"\nexport default ({ title, children }) => (\n  <section class=\"hero\">\n    <p class=\"hero__title\">{title}</p>\n    {children}\n  </section>\n);\n```\n\n```mdx displayName=\"docs/index.mdx\"\n---\ntitle: Kittens\n---\n\n# Kittens\n\n<Hero title=\"An API for cats\">\n  Feed, pet and schedule naps, from one small library.\n</Hero>\n```\n\nEach key in `components` is a tag, and its value the module whose default\nexport is the component, usually a `#theme/*` alias you define in `imports`.\nFor a named export, see\n[Components in MDX](./generators/html.html#components-in-mdx). Each page imports\nonly the components it uses, and some come built in and need no registering,\nsuch as `<DocumentationIndex />`.","summary":"Markdown pages have no components: their tags are plain HTML. To use components in a page, write it in MDX, register your components in `components`, and use them as tags:","examples":[{"language":"js","displayName":"doc-kit.config.mjs","code":"import { join } from 'node:path';\n\nexport default {\n  html: {\n    components: {\n      Hero: '#theme/Hero',\n    },\n    imports: {\n      '#theme/Hero': join(import.meta.dirname, 'theme/Hero.jsx'),\n    },\n  },\n};"},{"language":"jsx","displayName":"theme/Hero.jsx","code":"export default ({ title, children }) => (\n  <section class=\"hero\">\n    <p class=\"hero__title\">{title}</p>\n    {children}\n  </section>\n);"},{"language":"mdx","displayName":"docs/index.mdx","code":"---\ntitle: Kittens\n---\n\n# Kittens\n\n<Hero title=\"An API for cats\">\n  Feed, pet and schedule naps, from one small library.\n</Hero>"}],"children":[]},{"kind":"section","id":"interactive-components","name":"Interactive components","title":"Interactive components","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"Components render to HTML when the site is built, and load no JavaScript. A\ncomponent that has to run in the browser, with state, effects or event\nhandlers, has to be an island: doc-kit renders it at build time, then loads it\nin the browser and hydrates it once a condition you choose is met.\n\nWrap the component with `withIsland`, giving it a name and its conditions:\n\n```jsx displayName=\"theme/Counter.jsx\"\nimport withIsland from '@doc-kit/generator-react/html/ui/islands/withIsland.jsx';\nimport { useState } from 'preact/hooks';\n\nconst Counter = ({ start = 0 }) => {\n  const [count, setCount] = useState(start);\n\n  return (\n    <button type=\"button\" onClick={() => setCount(count + 1)}>\n      Petted {count} times\n    </button>\n  );\n};\n\nexport default withIsland(Counter, { name: 'Counter', on: { idle: true } });\n```\n\nThen list it in `components` under that same name, so the browser knows which\nmodule to load for it. Do this even for an island that only other components\nrender, never a page:\n\n```js displayName=\"doc-kit.config.mjs\"\nimport { join } from 'node:path';\n\nexport default {\n  html: {\n    components: {\n      Counter: '#theme/Counter',\n    },\n    imports: {\n      '#theme/Counter': join(import.meta.dirname, 'theme/Counter.jsx'),\n    },\n  },\n};\n```\n\n```mdx displayName=\"docs/kitten.mdx\"\n<Counter start={3} />\n```\n\nAn island missing from `components` stays static, and the browser console\nreports `[is-land] no component registered for \"Counter\"`.\n\n`on` takes [is-land](https://is-land.11ty.dev/)'s loading conditions, without\ntheir `on:` prefix, such as `{ idle: true }` or `{ visible: true }`. `idle`\nsuits most islands, and it's what this site's own islands use. The\n[Website reference](./generators/html.html#interactive-components) lists every\ncondition.\n\nWith `interaction: true`, the click that hydrates an island reaches it before\nits component has loaded, so the component never handles that click. An island\nthat has to handle its first click hydrates on\n`interaction: 'pointerover,focusin,touchstart'` instead, as doc-kit's code\nblocks do: it starts loading as the pointer or focus arrives, and a click that\nlands before it has loaded is replayed once it has.\n\nKeep in mind that:\n\n* Props are written into the page as JSON, so pass data only: strings,\n  numbers, booleans, arrays and plain objects, not functions.\n* Children are rendered at build time and kept as they are: the island\n  receives them as static HTML, which doesn't become interactive.\n* The island's first render in the browser has to match the HTML built for it.\n  Read browser-only state, such as `localStorage`, in an effect.\n* In browsers with the Navigation API, the site moves between its pages\n  without reloading, and islands are unmounted and hydrated again on every\n  page. Keep state that should outlast a page in module scope.","summary":"Components render to HTML when the site is built, and load no JavaScript. A component that has to run in the browser, with state, effects or event handlers, has to be an island: doc-kit renders it at build time, then loads it in the browser and hydrates it once a condition you choose is met.","examples":[{"language":"jsx","displayName":"theme/Counter.jsx","code":"import withIsland from '@doc-kit/generator-react/html/ui/islands/withIsland.jsx';\nimport { useState } from 'preact/hooks';\n\nconst Counter = ({ start = 0 }) => {\n  const [count, setCount] = useState(start);\n\n  return (\n    <button type=\"button\" onClick={() => setCount(count + 1)}>\n      Petted {count} times\n    </button>\n  );\n};\n\nexport default withIsland(Counter, { name: 'Counter', on: { idle: true } });"},{"language":"js","displayName":"doc-kit.config.mjs","code":"import { join } from 'node:path';\n\nexport default {\n  html: {\n    components: {\n      Counter: '#theme/Counter',\n    },\n    imports: {\n      '#theme/Counter': join(import.meta.dirname, 'theme/Counter.jsx'),\n    },\n  },\n};"},{"language":"mdx","displayName":"docs/kitten.mdx","code":"<Counter start={3} />"}],"children":[]},{"kind":"section","id":"page-layouts","name":"Page layouts","title":"Page layouts","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"`#theme/Layout` renders every page, so a layout of your own can give some\npages a different shape. This site's home page, for example, sets\n`layout: home` in its frontmatter, and the site's layout renders it full width,\nbetween the navigation bar and the footer, while every other page gets\ndoc-kit's documentation layout:\n\n```jsx displayName=\"theme/Layout.jsx\"\nimport DocLayout from '@doc-kit/generator-react/html/ui/components/Layout/index.jsx';\n\nimport Footer from '#theme/Footer';\nimport NavBar from '#theme/Navigation';\n\nconst HomeLayout = ({ metadata, children }) => (\n  <>\n    <NavBar metadata={metadata} />\n    <div class=\"home\" role=\"main\">\n      {children}\n    </div>\n    <Footer />\n  </>\n);\n\nconst LAYOUTS = { home: HomeLayout };\n\nexport default props => {\n  const Layout = LAYOUTS[props.metadata.layout] ?? DocLayout;\n\n  return <Layout {...props} />;\n};\n```\n\n```mdx displayName=\"docs/index.mdx\"\n---\ntitle: Kittens\nlayout: home\n---\n\n<Hero title=\"An API for cats\" />\n```\n\nPoint `#theme/Layout` at it in `imports`. A layout receives the page's\n`metadata`, every field of its frontmatter included, its `headings`, its\n`readingTime` and its content as `children`; the\n[Website reference](./generators/html.html#layout-props) describes each.\n\ndoc-kit also builds a `404.html` page, whose `metadata.api` is `'404'`: check\nfor it to give that page a layout of its own.\n\n> \\[!TIP]\n> doc-kit's stylesheet styles the headings inside `<main>` as API entries. A\n> layout for other kinds of pages can use another element with\n> `role=\"main\"`, as above.","summary":"`#theme/Layout` renders every page, so a layout of your own can give some pages a different shape. This site's home page, for example, sets `layout: home` in its frontmatter, and the site's layout renders it full width, between the navigation bar and the footer, while every other page gets doc-kit's documentation layout:","examples":[{"language":"jsx","displayName":"theme/Layout.jsx","code":"import DocLayout from '@doc-kit/generator-react/html/ui/components/Layout/index.jsx';\n\nimport Footer from '#theme/Footer';\nimport NavBar from '#theme/Navigation';\n\nconst HomeLayout = ({ metadata, children }) => (\n  <>\n    <NavBar metadata={metadata} />\n    <div class=\"home\" role=\"main\">\n      {children}\n    </div>\n    <Footer />\n  </>\n);\n\nconst LAYOUTS = { home: HomeLayout };\n\nexport default props => {\n  const Layout = LAYOUTS[props.metadata.layout] ?? DocLayout;\n\n  return <Layout {...props} />;\n};"},{"language":"mdx","displayName":"docs/index.mdx","code":"---\ntitle: Kittens\nlayout: home\n---\n\n<Hero title=\"An API for cats\" />"}],"children":[]},{"kind":"section","id":"markdown-plugins","name":"Markdown plugins","title":"Markdown plugins","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"The pages' Markdown is rendered by the `jsx-ast` generator, which takes your\nown [unified](https://unifiedjs.com/) plugins: remark plugins for the Markdown,\nrehype plugins for the HTML, and recma plugins for the JavaScript it compiles\nto. This site uses a rehype plugin to inline its diagrams as SVG, so they\nfollow the color scheme. See\n[Markdown plugins](./configuration.html#markdown-plugins).","summary":"The pages' Markdown is rendered by the `jsx-ast` generator, which takes your own unified plugins: remark plugins for the Markdown, rehype plugins for the HTML, and recma plugins for the JavaScript it compiles to. This site uses a rehype plugin to inline its diagrams as SVG, so they follow the color scheme. See Markdown plugins.","examples":[],"children":[]},{"kind":"section","id":"the-bundler","name":"The bundler","title":"The bundler","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"[Vite](https://vite.dev/) builds the site by default. To pass it plugins or\noptions, create its adapter yourself, with the Vite configuration to merge in:\n\n```js displayName=\"doc-kit.config.mjs\"\nimport { createViteBundler } from '@doc-kit/generator-react/html/bundlers/vite';\n\nimport myVitePlugin from './my-vite-plugin.mjs';\n\nexport default {\n  // A bundler adapter needs a single thread, see below\n  threads: 1,\n\n  html: {\n    bundler: createViteBundler({\n      plugins: [myVitePlugin()],\n      define: {\n        'import.meta.env.STATS_ID': JSON.stringify('kittens'),\n      },\n    }),\n  },\n};\n```\n\n> \\[!WARNING]\n> Worker threads can't receive functions, so a configuration that sets\n> `bundler`, the Vite adapter included, has to build with `threads: 1` (or\n> `--threads 1`). With more threads, the build fails with a `DataCloneError`.\n\nYour plugins run after doc-kit's own and see every module of both builds, but\nnot the HTML pages: change those through the [template](#the-page-template).\nThe scripts and styles are minified unless you set `build.minify: false`; the\n`minify` option only covers the HTML. A few options, such as the base URL and\nthe JSX runtime, are doc-kit's to set; the\n[Vite adapter](./generators/html.html#vite-adapter) reference lists them.\n\nAnother bundler, such as webpack or Rspack, plugs in through a small adapter\nof your own, which needs `threads: 1` as well. The\n[Website reference](./generators/html.html#bundler-adapters) describes how to\nwrite one.","summary":"Vite builds the site by default. To pass it plugins or options, create its adapter yourself, with the Vite configuration to merge in:","examples":[{"language":"js","displayName":"doc-kit.config.mjs","code":"import { createViteBundler } from '@doc-kit/generator-react/html/bundlers/vite';\n\nimport myVitePlugin from './my-vite-plugin.mjs';\n\nexport default {\n  // A bundler adapter needs a single thread, see below\n  threads: 1,\n\n  html: {\n    bundler: createViteBundler({\n      plugins: [myVitePlugin()],\n      define: {\n        'import.meta.env.STATS_ID': JSON.stringify('kittens'),\n      },\n    }),\n  },\n};"}],"children":[]},{"kind":"section","id":"a-complete-example","name":"A complete example","title":"A complete example","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"This site is built with doc-kit, from the\n[`www/`](https://github.com/nodejs/doc-kit/tree/main/www) directory of its\nrepository, and its\n[configuration file](https://github.com/nodejs/doc-kit/blob/main/www/doc-kit.config.mjs)\nuses most of this page:\n\n* A [layout](https://github.com/nodejs/doc-kit/blob/main/www/theme/Layout.jsx)\n  with a `home` layout for the\n  [home page](https://github.com/nodejs/doc-kit/blob/main/www/pages/index.mdx),\n  a 404 page, and doc-kit's layout for everything else.\n* A logo and a footer of its own.\n* Islands for the home page's animations and tabs, listed in `components`.\n* A stylesheet that swaps the typeface for Inter and keeps doc-kit's neutral\n  palette, and a template that preloads Inter instead of Open Sans.\n* A `#theme/site` virtual module with doc-kit's version, for the footer, and\n  the JSON schema's, for the home page.\n* A rehype plugin that inlines the diagrams, and `pathsToCopy` for its public\n  files, fonts and JSON schemas.\n\nWhen your site looks right, [Publishing your docs](./publishing.html) covers\nputting it online.","summary":"This site is built with doc-kit, from the `www/` directory of its repository, and its configuration file uses most of this page:","examples":[],"children":[]}]}