---
description: Customize Doctrine with CSS variables, stable slots, and optional Tailwind CSS.
---

# Customization

Doctrine has two extension points: a stylesheet for appearance and a React component map for MDX.
Both are project-level customization. See [MDX components](/mdx-components/) for the built-in
component reference.

## Add a stylesheet

Set `styles` to an existing file resolved from the project root:

```ts
import { defineConfig } from '@amamo/doctrine'

export default defineConfig({
  styles: './docs/theme.css',
})
```

Doctrine imports this file after its precompiled runtime CSS. Override only what the site needs:

```css
:root {
  --doctrine-accent: oklch(0.9 0.05 155);
  --doctrine-accent-foreground: oklch(0.25 0.04 155);
  --doctrine-separator: oklch(0.88 0.025 155);
  --doctrine-sidebar: oklch(0.97 0.01 155);
  --doctrine-ring: oklch(0.48 0.12 155);
  --doctrine-radius: 0.9rem;
  --doctrine-font-sans: Inter, sans-serif;
  --doctrine-font-mono: 'JetBrains Mono', monospace;
  --doctrine-content-width: 52rem;
}

[data-theme='dark'] {
  --doctrine-background: oklch(0.14 0.02 250);
  --doctrine-muted: oklch(0.21 0.02 250);
}
```

### Theme variables

| Variable                                                            | Controls                             |
| ------------------------------------------------------------------- | ------------------------------------ |
| `--doctrine-background`, `--doctrine-foreground`                    | Page background and primary text     |
| `--doctrine-card`, `--doctrine-card-foreground`                     | Raised card surfaces                 |
| `--doctrine-popover`, `--doctrine-popover-foreground`               | Menus, selects, and dialogs          |
| `--doctrine-primary`, `--doctrine-primary-foreground`               | Primary actions and links            |
| `--doctrine-secondary`, `--doctrine-secondary-foreground`           | Secondary surfaces                   |
| `--doctrine-muted`, `--doctrine-muted-foreground`                   | Muted surfaces and secondary text    |
| `--doctrine-accent`, `--doctrine-accent-foreground`                 | Hovered and selected controls        |
| `--doctrine-border`, `--doctrine-separator`                         | Strong borders and subtle separators |
| `--doctrine-input`, `--doctrine-ring`                               | Input boundaries and keyboard focus  |
| `--doctrine-sidebar`, `--doctrine-sidebar-foreground`               | Navigation surface and text          |
| `--doctrine-sidebar-accent`, `--doctrine-sidebar-accent-foreground` | Navigation hover and selection       |
| `--doctrine-code`, `--doctrine-code-foreground`                     | Code block surface and text          |
| `--doctrine-radius`                                                 | Shared surface radius                |
| `--doctrine-font-sans`, `--doctrine-font-display`                   | UI, body, and heading font stacks    |
| `--doctrine-font-mono`                                              | Code font stack                      |
| `--doctrine-content-width`                                          | Maximum article width                |
| `--doctrine-sidebar-width`, `--doctrine-toc-width`                  | Desktop navigation and TOC widths    |
| `--doctrine-header-height`                                          | Sticky header height                 |

Put light values in `:root` and dark overrides in `[data-theme='dark']`.

## Target stable layout slots

Use `data-slot` instead of depending on generated utility classes:

```css
[data-slot='header'] {
  box-shadow: 0 1px 0 color-mix(in oklab, var(--doctrine-ring) 25%, transparent);
}

[data-slot='brand'] {
  color: var(--doctrine-accent);
}

[data-slot='content'] {
  letter-spacing: 0.005em;
}
```

The shell exposes `header`, `header-inner`, `brand`, `navigation`, `sidebar`, `main`, `content`,
`page-actions-row`, `page-actions`, `page-actions-copy`, `page-actions-menu-trigger`,
`page-actions-menu`, `page-actions-markdown`, `page-actions-source`, `page-navigation`, and `footer`.
Built-ins expose `badge`, `callout`, `card`, `card-grid`, `code-block`,
`code-block-header`, `code-block-filename`, `code-block-language`, `code-block-copy`, `file-tree`,
`file-tree-list`, `file-tree-folder`, `file-tree-file`, `live-preview`, `live-preview-title`,
`live-preview-canvas`, `live-preview-code`, `live-preview-code-toggle`, `live-preview-source`, `step`,
`steps`, `table-scroll`, `tabs`, `tab-list`, `tab`, and `tab-panel`.

## Use Tailwind CSS

Doctrine's own interface is already compiled, so consumers do not need Tailwind. To use utilities
inside MDX or custom components, declare Tailwind in the documentation project's root manifest:

<InstallTabs dev packageName="tailwindcss" />

Then opt the configured stylesheet into Tailwind and state what it should scan:

```css
@import 'tailwindcss' source(none);
@source './';
```

Doctrine checks the root `package.json` dependencies, dev dependencies, and optional dependencies.
When one declares `tailwindcss`, it enables `@tailwindcss/vite` in both commands. Without that
declaration, `styles` remains ordinary CSS.

## Register or override components

Set `components` to a module with a default component map. Custom entries merge after the built-ins,
so equal names replace them:

```tsx
import type { IDoctrineComponents } from '@amamo/doctrine'
import type { ComponentProps } from 'react'
import { Callout } from '@amamo/doctrine/components'

function BrandedCallout(props: ComponentProps<typeof Callout>) {
  return <Callout className="branded-callout" {...props} />
}

export default { Callout: BrandedCallout } satisfies IDoctrineComponents
```

```ts
export default defineConfig({
  components: './docs/components.tsx',
})
```

Define `.branded-callout` in `styles`, or use utilities after enabling Tailwind. Registering `a`
replaces Doctrine's base-aware link component, so the replacement must preserve any desired subpath
and external-link behavior.

Custom modules are executable in both Node.js SSR and the browser. Keep module initialization and
rendering free of browser-only globals; use effects or event handlers for browser access.
