Skip to content
API reference

content

Every export of @kitr/content, @kitr/content/react, @kitr/content/markdown, @kitr/content/types, @kitr/content/vite, read from the published type definitions. The guide shows them in use.

@kitr/content

function search<E extends SearchEntry>(entries: E[], query: string, limit?: number): E[];

Rank search entries against a query. Every word must match somewhere: in the title it is worth 6, in the heading 4 and in the text 1, and an exact title gets 10 more. A whole page beats one of its sections, and at most two results come from one page so one long page cannot fill the list.

search(await loadSearch(), 'deploy static', 8)

defineConfig

const defineConfig: <const C extends ContentConfig>(config: C) => C;

Types the default export of config/content.ts, which kitr generate reads.

CodeConfig

interface CodeConfig {
  /** Default `{ light: 'github-light-high-contrast', dark: 'github-dark' }`. */
  themes?: {
    light: string;
    dark: string;
  };
  /** Languages to load. A fence in any other language renders as plain text. Default: ts, tsx, js, jsx, json, sh, bash, css, html, md, yaml, diff. */
  langs?: string[];
}

The code highlighter: Shiki themes for light and dark, and the languages it loads.

CollectionConfig

interface CollectionConfig {
  /** The folder of `*.md` files, relative to the project root. */
  source: string;
  /** The URL prefix each page is served under: `/docs` serves `getting-started.md` at `/docs/getting-started`. */
  url: string;
  /**
   * A JSON file in `source` that orders and groups the pages (`nav.json`). Without it every `*.md`
   * is listed in one group, sorted by the frontmatter `order`, then by title.
   */
  nav?: string;
  /** The group title when there is no `nav`, and the section search shows. Default: the collection name, capitalised. */
  title?: string;
}

One folder of markdown pages served under one URL prefix: docs: { source: 'content/docs', url: '/docs' }.

ContentConfig

interface ContentConfig {
  /** The collections by name. The name is what `loadPage(name, slug)` and `nav[name]` use. */
  collections: Record<string, CollectionConfig>;
  /** Write `llms.txt`, `llms-full.txt` and `<url>.md` for every page, for coding agents. Default `false`. */
  llms?: boolean | LlmsConfig;
  /** Shiki themes and languages for code blocks. */
  code?: CodeConfig;
  /**
   * The start of an "edit this page" link; the source file's path from the project root is appended:
   * `https://github.com/acme/docs/edit/main` gives `.../edit/main/content/docs/cli.md`.
   */
  editUrl?: string;
}

The options in config/content.ts. kitr generate reads them; the app never does at run time.

LlmsConfig

interface LlmsConfig {
  /** The `# heading` of `llms.txt`. Default: the `name` in package.json as words (`acme-docs` is `Acme Docs`). */
  title?: string;
  /** The `> summary` line under the heading. */
  description?: string;
  /** The site's origin (`https://docs.acme.com`), so the links in `llms.txt` are absolute. Default: relative links. */
  url?: string;
}

What llms: { ... } sets for llms.txt. llms: true uses the defaults.

interface NavGroup<S extends string = string> {
  title: string;
  description?: string;
  items: NavItem<S>[];
}

A titled group of pages, in the order nav.json lists them.

interface NavItem<S extends string = string> {
  /** The file name without `.md`, and the last segment of the page's URL. */
  slug: S;
  title: string;
  description?: string;
}

One page in a nav group. S is the collection's slug union in the generated #kitr/content.

Page

interface Page {
  /** The page's path: `/docs/getting-started`. */
  url: string;
  /** From `nav.json`, the frontmatter or the leading `# heading`, in that order. */
  title: string;
  /** From `nav.json` or the frontmatter. */
  description?: string;
  /** The nav group the page is listed under. */
  section: string;
  /** The frontmatter, as written. */
  data: Record<string, string | number>;
  /** The body: HTML and the `kitr` commands between it. */
  blocks: Block[];
  /** The page's h2 and h3 headings, in order. */
  toc: TocEntry[];
  /** The page has numbered `## 1. Step` headings. */
  steps: boolean;
  /** The "edit this page" link, when `editUrl` is set. */
  editUrl?: string;
}

A rendered page, as loadPage(collection, slug) returns it.

SearchEntry

interface SearchEntry {
  /** The page path, with `#id` for a section. */
  url: string;
  /** The page title. */
  title: string;
  /** The nav group, shown next to the result. */
  section: string;
  /** The section heading; empty for the page itself. */
  heading: string;
  /** The plain text of the page's intro or of the section, for matching and the snippet. */
  text: string;
}

One search result: a whole page (heading empty) or one of its h2/h3 sections.

TocEntry

interface TocEntry {
  /** 2 for an h2, 3 for an h3. */
  depth: number;
  /** The heading's `id`, for `#id` links. */
  id: string;
  /** The heading as plain text, without a step number. */
  text: string;
}

One h2 or h3 of a page, for the table of contents. id is the heading's anchor.

Block

type Block = {
  type: 'html';
  html: string;
} | {
  type: 'cmd';
  cmd: string;
};

A piece of a rendered page: HTML, or a kitr ... command the app renders with a package-manager picker.

@kitr/content/react

DocBody

function DocBody({ page, renderCommand, className }: DocBodyProps): import("react").JSX.Element;

A page's rendered markdown: the HTML blocks from kitr generate with each kitr command rendered by renderCommand between them. It adds a Copy button to every code block and keeps clicks on internal links (/docs/cli) in the app's router instead of reloading the page.

Toc

function Toc({ page, title, active, className }: TocProps): import("react").JSX.Element | null;

"On this page": the page's h2 and h3 as links, the one being read marked, and an edit link when the page has one.

useActiveHeading

function useActiveHeading(toc: TocEntry[], offset?: number): string | undefined;

The id of the heading being read: the last one whose top has scrolled above offset pixels (the height of a sticky header, plus some), or the last heading once the page is at its end.

DocBodyProps

interface DocBodyProps {
  /** The page from `loadPage(collection, slug)`. Only `blocks` and `steps` are read. */
  page: Pick<Page, 'blocks' | 'steps'>;
  /**
   * Render a `kitr ...` command block, such as a package-manager picker:
   * `renderCommand={(cmd) => <Command cmd={cmd} />}`. Default: a plain code block.
   */
  renderCommand?: (cmd: string) => ReactNode;
  /** Classes added after `prose`. */
  className?: string;
}

Props of <DocBody>.

TocProps

interface TocProps {
  /** The page from `loadPage(collection, slug)`. `toc` and `editUrl` are read. */
  page: Pick<Page, 'toc' | 'editUrl'>;
  /** The label above the list and the navigation's name. Default `On this page`. */
  title?: string;
  /** The heading to mark as current. Default: `useActiveHeading(page.toc)`. */
  active?: string;
  className?: string;
}

Props of <Toc>.

@kitr/content/markdown

parseFrontmatter

function parseFrontmatter(source: string): {
  data: Record<string, string | number>;
  body: string;
};

Split --- frontmatter from the body. Each key: value line becomes a string; order becomes a number. Quotes around a value are dropped.

renderMarkdown

function renderMarkdown(markdown: string, options?: RenderOptions): Promise<RenderedMarkdown>;

Render one markdown body (no frontmatter) to blocks, a table of contents and search text. The leading # heading is returned as heading and left out of the HTML: pages render their title themselves.

const page = await renderMarkdown('# Install\n\n```sh\nkitr add content\n```\n')
// => { heading: 'Install', blocks: [{ type: 'cmd', cmd: 'kitr add content' }], ... }

defaultLangs

const defaultLangs: readonly [
  "ts",
  "tsx",
  "js",
  "jsx",
  "json",
  "sh",
  "bash",
  "css",
  "html",
  "md",
  "yaml",
  "diff"
];

The languages loaded when code.langs is not set.

defaultThemes

const defaultThemes: {
  readonly light: "github-light-high-contrast";
  readonly dark: "github-dark";
};

The Shiki themes used when code.themes is not set.

RenderedMarkdown

interface RenderedMarkdown {
  /** The leading `# heading`, which is dropped from the body. */
  heading?: string;
  blocks: Block[];
  toc: TocEntry[];
  /** True when the page has numbered `## 1. Step` headings. */
  steps: boolean;
  /** The plain text before the first h2, for search. */
  intro: string;
  sections: RenderedSection[];
}

What renderMarkdown returns.

RenderedSection

interface RenderedSection {
  id: string;
  heading: string;
  text: string;
}

One h2 or h3 and the plain text under it, for search.

RenderOptions

interface RenderOptions {
  /** Shiki themes and languages. */
  code?: CodeConfig;
  /**
   * Rewrite a relative link to another markdown file (`./cli.md#flags`): return the page URL
   * (`/docs/cli#flags`), or `undefined` to keep the link as written.
   */
  resolveLink?: (href: string) => string | undefined;
}

Options for renderMarkdown.

@kitr/content/types

contentFiles

function contentFiles(content: BuiltContent): GeneratedFile[];

The generated files for built content, relative to .kitr/.

loadContentConfig

function loadContentConfig(root: string, configDir?: string): Promise<ContentConfig | undefined>;

Run config/content.ts with jiti from the project root, the way the CLI runs kitrrc.ts, so the file can import defineConfig from @kitr/content and compute values. undefined when the project has no such file.

renderIndex

function renderIndex(content: Pick<BuiltContent, 'nav'>): string;

.kitr/content/index.ts: the nav inline, typed slugs, and lazy loaders for pages and search.

OUT_DIR

const OUT_DIR = "content";

The folder under .kitr/ this generator writes.

@kitr/content/vite

kitrContent

function kitrContent(options?: ContentViteOptions): Plugin;

The content package's Vite plugin, added by @kitr/vite because the package declares it. When llms is on in config/content.ts, the dev server answers /llms.txt, /llms-full.txt and <page url>.md, and a build writes the same files next to the HTML in the client output, so a static or server-rendered site ships them. The text comes from .kitr/content/llms.json and the markdown sources; nothing is rendered twice.

ContentViteOptions

interface ContentViteOptions {
  /** Where `kitr generate` wrote `llms.json`, relative to the root. Default `.kitr/content`. */
  dir?: string;
}

Options for the content Vite plugin.