# Writing a theme (/docs/theming/authoring)

A theme is a small manifest plus React components that yaakdoc renders on the server with Vite. You write the layout and the styles; yaakdoc's unstyled components and hooks provide the content.

```npm
npm i @yaakdoc/react react react-dom
```

<Callout title="Start from Kiseki">
  The quickest start is often [ejecting Kiseki](/docs/theming/ejecting) and changing it. It's a
  complete theme that uses everything on this page.
</Callout>

## Anatomy of a theme [#anatomy-of-a-theme]

```text
my-theme/
├── theme.ts          manifest, loaded by yaakdoc in Node
└── src/
    ├── entry.tsx     server entry: { Layout, components?, Head? }
    ├── client.ts     client entry: the islands to hydrate
    └── styles.css    styles (any CSS Vite supports: plain CSS, Tailwind, ...)
```

Use it with `"theme": "./my-theme"`.

## The manifest [#the-manifest]

```ts title="theme.ts"
// Keep this file free of React imports: yaakdoc loads it in Node.
import { defineTheme } from "@yaakdoc/react/theme";
import { z } from "zod";

export default defineTheme({
  name: "my-theme",
  entry: "./src/entry.tsx",
  client: "./src/client.ts",
  styles: ["./src/styles.css"],

  // Font files (from your styles) to preload, so the first paint doesn't use a fallback font.
  // A role is skipped when the user sets `branding.fonts.sans` / `branding.fonts.mono`.
  preloadFonts: {
    sans: ["inter-latin-wght-normal.woff2"],
  },

  // Shiki themes for syntax highlighting.
  highlight: {
    light: "github-light",
    dark: "github-dark",
  },

  // Validates `themeOptions` and fills in defaults.
  options: z.object({
    accent: z.string().default("violet"),
  }),

  // Vite plugins the theme needs, e.g. Tailwind CSS.
  vitePlugins: async () => [
    (await import("@tailwindcss/vite")).default()
  ],

  // What `yaakdoc eject` copies, and the packages it lists.
  eject: {
    files: ["theme.ts", "src/**"],
    dependencies: ["@yaakdoc/react"],
  },
});
```

Theme source is processed by Vite, so TSX works without a build step, and a theme package can ship its source as is.

<Callout type="warn" title="Load fonts from styles">
  Load fonts from `styles`, not from the client entry. CSS imported by the client entry is only
  applied once its script runs (in dev), so text first renders in a fallback font. To avoid a
  visible switch when the font arrives, preload the fonts with `preloadFonts` and declare them with
  `font-display: optional`.
</Callout>

## The layout [#the-layout]

`entry.tsx` default-exports a `ThemeRuntime`. Its `Layout` renders the page `<body>`. yaakdoc renders `<html>` and `<head>`: meta tags, assets, the branding CSS variables and the color mode script.

```tsx title="src/entry.tsx"
import {
  Content,
  Footer,
  Logo,
  NavTree,
  type ThemeRuntime,
} from "@yaakdoc/react";

import { ColorModeToggle, NavFilter } from "@yaakdoc/react/islands";

const theme: ThemeRuntime = {
  Layout: ({ page }) => (
    <div className="layout">
      <aside id="sidebar">
        <Logo />
        <NavFilter target="#sidebar [data-yd=nav]" />
        <NavTree />
      </aside>

      <ColorModeToggle />

      <Content />

      <Footer />
    </div>
  ),
};

export default theme;
```

`<Content />` renders whatever the current page is. `page.kind` tells you which kind it is:

| `kind` | Page |
| --- | --- |
| `page` | A [custom page](/docs/guides/custom-pages) |
| `index` | The API overview |
| `folder` | A folder (with `"pages": "per-request"`) |
| `request` | A request (with `"pages": "per-request"`) |
| `not-found` | The 404 page |

In `pages: "single"` mode, the index page contains the whole reference. `<Content />` handles both modes, but you can compose the pieces yourself.

## Data hooks [#data-hooks]

| Hook | Returns |
| --- | --- |
| `useDocument()` | The `PreparedDocument`: `tree`, `requests`, `folders`, `workspace`, `environment`, `variables`, `pages`, `navigation`, `referenceBase`. |
| `useSite()` | Site data: title, description, logo, colors, links, `llms` settings, `base`, `pages`, ... |
| `usePage()` | The current page (`kind`, `id`, `path`, `markdownUrl`, ...). |
| `useRequest(id)`, `useFolder(id)`, `usePageDoc(id)` | A single prepared request, folder or custom page. |
| `useNavigation()` | The navigation: `tabs`, each with `items` (pages, groups, and `{ type: "api" }` for the folders and requests). |
| `useActiveTab()` | The tab of the current page (the API reference's tab for the overview, folders and requests). |
| `usePageNeighbors(id)` | The previous and next entries around a page, in navigation order. |
| `useThemeOptions<T>()` | Your validated `themeOptions`. |

A prepared request already contains everything a theme needs:

- `href` and `markdownUrl`
- `resolvedUrl`, with variables resolved and secrets as `{{placeholders}}`
- `descriptionHtml`, sanitized Markdown
- `bodyHtml`, `examples[].html` and `responses[]` (Shiki-highlighted example responses with `status`, `label` and `language`)
- headers, params and auth, each with `inheritedFrom` information

A prepared custom page has `title`, `description`, `sidebarTitle`, `icon`, `href`, `markdownUrl`, `summary` and `html`.

## Components [#components]

All components are unstyled. They render semantic markup with stable `data-yd="..."` attributes, and accept `className`.

| Area | Components |
| --- | --- |
| Page structure | `Content`, `Overview`, `TreeContent`, `FolderDoc`, `ChildLinks`, `NotFound`, `Heading` |
| Custom pages | `PageDoc`, `PageNav` (previous and next links) |
| Requests | `RequestDoc`, `RequestBody`, `MethodBadge`, `RequestUrl`, `AuthInfo`, `KeyValueTable`, `Variables` |
| Code and Markdown | `CodeBlock`, `CodeExamples`, `ResponseExamples`, `Markdown`, `Html` |
| Environments | `EnvironmentVariants`, `EnvironmentSelect` |
| Navigation | `Navigation` (a tab's groups, pages and the API reference), `NavTabs`, `NavTree` (the API reference alone), `NavIcon` (renders nothing; override it to show icons), `Breadcrumbs` |
| Search | `Search` (a `SearchTrigger` and a `SearchDialog`) |
| Site chrome | `Logo`, `SiteLinks`, `Footer`, `PageActions`, `RunInYaak`, `BrandingStyles` |

A few attributes are useful when styling with plain CSS:

- `[data-method="get"]` on method badges.
- `[data-yd="url-variable"]` on the `{{variables}}` inside a `RequestUrl`.
- `[data-yd-section]` on documented sections, which the scroll spy uses.
- `aria-current` on the active navigation link.

## Overriding components [#overriding-components]

Every component above can be replaced everywhere, including inside other components, by registering an override under its name. The override receives the same props, plus `Default`, the original component:

```tsx title="src/entry.tsx"
const theme: ThemeRuntime = {
  Layout,
  components: {
    MethodBadge: ({ request, Default }) => {
      return (
        <Default request={request} className="badge" />
      );
    },
  },
};
```

Use `overridable(name, Component)` to make your own components overridable.

<Callout title="Override, don't re-implement">
  `<Content />` decides what each page shows: a custom page, the API reference, a folder, a request
  or the 404 page. Keep that logic and restyle the parts. Kiseki, for example, renders
  `<Content />` and overrides `Overview`, `FolderDoc`, `RequestDoc`, `ChildLinks`, `PageDoc`,
  `PageNav`, `NavIcon` and `NotFound`.
</Callout>

### Helpers [#helpers]

These keep themes consistent with the built-in components:

| Helper | Purpose |
| --- | --- |
| `usePageActions(markdownUrl)` | Data for "Copy page/section" actions (`null` when disabled). |
| `copyActionLabel(level)` | "Copy page" or "Copy section", depending on the heading level. |
| `useRunInYaak()` | Name and export URL for the "Run in Yaak" button (`null` when disabled). |
| `toAbsoluteUrl(site, path)` | The absolute URL of a site path, when `branding.url` is set. |
| `tabGroupIds`, `tabPanelProps`, `visibleTabPanelSelector` | Ids, panel props and copy targets for server-rendered panels controlled by `TabList`. |
| `methodLabel`, and `grpcMethodPath`, `formFieldValue` from `@yaakdoc/core` | Display values for methods, gRPC calls and form fields. |

## Branding tokens [#branding-tokens]

The user's [branding](/docs/guides/branding) arrives as CSS custom properties on `:root`:

- `--yd-color-primary`, `--yd-color-accent`, `--yd-color-background` and `--yd-color-foreground`, with per-mode values under `[data-theme="light"]` and `[data-theme="dark"]`
- `--yd-font-sans` and `--yd-font-mono`

Use them with fallbacks:

```css title="src/styles.css"
:root {
  --primary: var(--yd-color-primary, #6d28d9);
}
```

### Color mode [#color-mode]

The color mode is applied before first paint as `data-theme="light|dark"` on `<html>`, and `color-scheme` is set to match. Without JavaScript, follow `prefers-color-scheme`.

Highlighted code uses Shiki's dual themes. Pick the right variant with:

```css title="src/styles.css"
.shiki,
.shiki span {
  color: light-dark(var(--shiki-light), var(--shiki-dark));
}
```

## Environments [#environments]

When the config lists several [`environments`](/docs/guides/environments#environment-switcher), environment-dependent data is prepared once per environment:

- `request.variants` holds `resolvedUrl` and `examples` for each environment.
- `document.variableSets` holds the variables table for each environment.
- `document.environments` lists the options, default first.
- `page.html` of custom pages contains a variant of each block that uses variables, wrapped in `[data-yd-env]`, so the switcher works on pages without extra code.

Render the variants with `<EnvironmentVariants variants={...}>{(variant) => ...}</EnvironmentVariants>`, and add a switcher with `<EnvironmentSelect />` (or build your own with `useEnvironment`). yaakdoc adds a small head script and CSS, so a remembered choice shows up before first paint.

## Entry points [#entry-points]

| Import | Contents |
| --- | --- |
| `@yaakdoc/react` | Components, hooks, overrides, types. |
| `@yaakdoc/react/islands` | [Islands](/docs/theming/islands) and headless hooks (client-safe). |
| `@yaakdoc/react/client` | `hydrateIslands()` (used by yaakdoc's client entry). |
| `@yaakdoc/react/server` | `renderPage()` (used by yaakdoc's SSR entry). |
| `@yaakdoc/react/theme` | `defineTheme()` and theme types (Node-safe). |
