On this page

This page walks the customizations most projects make, in the order they usually make them. The complete option list lives in the html generator reference.

All of it goes in the html section of your configuration file:

export default {
  target: ['html'],
  global: {
    input: ['docs/**/*.md'],
    output: 'out',
  },

  html: {
    // ... everything on this page
  },
};
html: {
  project: 'My Project', // Used in page titles and the version selector
  title: '{project} Documentation', // Browser-tab title template
  // Suppress the Node.js announcement banner fetched at runtime
  remoteConfigUrl: '',
},

title supports {project} and {version} placeholders.

head controls the markup injected into every page's <head>:

html: {
  head: {
    meta: [
      { name: 'description', content: 'My project documentation' },
      { property: 'og:image', content: 'https://example.com/og.png' },
    ],
    links: [{ rel: 'icon', href: '/favicon.ico' }],
    html: [
      `<style>
        :root, :root.dark {
          --color-brand-400: #b8adff;
          --color-brand-600: #6e5cd9;
        }
      </style>`,
    ],
  },
},

The <style> override above re-brands the accent color: the UI derives its palette from --color-brand-* custom properties, so a few lines of CSS restyle the whole site.

Without configuration, the sidebar is one group holding every page. The navigation key allows you to customize it:

html: {
  navigation: {
    sidebar: [
      {
        groupName: 'Guides',
        items: [{ label: 'Getting started', link: '/getting-started' }],
      },
      {
        groupName: 'Reference',
        items: [{ label: '`api`', link: '/api' }],
      },
    ],
    navbar: [
      {
        text: 'GitHub',
        link: 'https://github.com/nodejs/doc-kit',
        target: '_blank',
      },
    ],
  },
},

Sidebar links are page paths without extensions; backticked spans in a label render as code. Items can nest through their own items array. The sidebar also shows a version selector when your configuration provides a changelog.

Every piece of the page surrounding your content is a component you can replace. Override any #theme/* alias with a path to your own:

html: {
  imports: {
    '#theme/Logo': './src/MyLogo.jsx',
    '#theme/Footer': './src/MyFooter.jsx',
  },
},
export default () => (
  <svg height="30" width="30" viewBox="0 0 10 10">
    <circle cx="5" cy="5" r="5" fill="var(--color-brand-400)" />
  </svg>
);

Available slots: #theme/Logo, #theme/Navigation, #theme/Sidebar, #theme/Metabar, #theme/Footer (renders nothing by default), and #theme/Layout (the outermost wrapper). Custom components can import whatever build-time data they need — project name, version list, page index — from the #theme/config virtual module; see the html reference for its exports.

Register your own JSX components and use them directly in content:

html: {
  components: {
    Hero: '#theme/Hero',
  },
  imports: {
    '#theme/Hero': './src/components/Hero.jsx',
  },
},
---
title: Welcome
---

# Welcome

<Hero title="My Project" />

Author component-bearing pages as .mdx files (or set mdx: true in a page's frontmatter). Regular .md files treat < and { literally, which is what API documentation wants; MDX pages get real JSX and skip API-doc signature parsing while keeping headings, TOC, and sidebar behavior.

Vite builds the site by default, and accepts your plugins and options:

import { createViteBundler } from '@doc-kit/generator-react/html/bundlers/vite';

export default {
  html: {
    bundler: createViteBundler({
      plugins: [myVitePlugin()],
    }),
  },
};

A different bundler entirely (webpack, Rspack, …) can be supplied as a small adapter object — see Bundler adapters in the reference.