# Theming & CSS

Every color, radius, and font in the shell resolves through prefixed
CSS custom properties declared in `@dreamlake/dockit/styles.css`. The
`--doc-template-` prefix isolates the shell from the consuming app's
own tokens; restyling the shell is overriding variables — no component
edits.

## The consumer stylesheet

A dockit site owns exactly one CSS file. This site's is the whole
recipe:

```css file="styles/app.css"
@import "tailwindcss";
@import "@dreamlake/dockit/styles.css";

/* Tell Tailwind where this site's own utility-class usage lives (the
   library's dist is covered by the @source inside its styles.css). */
@source "../pages";
@source "../examples";
@source "../renderer";
```

Line by line:

- `@import "tailwindcss"` boots Tailwind v4 — no `tailwind.config.js`,
  everything is CSS-first.
- `@import "@dreamlake/dockit/styles.css"` pulls in the shell's
  `@theme` token block, dark-mode overrides, and component styles.
  Order matters: the library must come **after** tailwindcss and
  **before** your own overrides.
- Your `@source` lines point Tailwind at the directories where *your*
  markup uses utility classes, so those utilities get generated.
- The library's stylesheet carries its own `@source "./"` at the top.
  Because `@source` resolves relative to the file it appears in, and
  the file ships as `dist/styles.css`, that line makes Tailwind scan
  the package's compiled JS in `dist/` — classes used only inside
  dockit components are always emitted, without you listing the
  package yourself.

## Color tokens

Declared in an `@theme` block, so each also mints Tailwind utilities
(`bg-doc-template-bg`, `text-doc-template-ink`, …):

| Token | Light | Role |
| --- | --- | --- |
| `--color-doc-template-bg` | `#fffefa` | Page background |
| `--color-doc-template-panel` | `#fcfbf7` | Cards, footer panels |
| `--color-doc-template-rail` | `#fcfbf7` | Sidebar / rail background |
| `--color-doc-template-code` | `#faf8f1` | Code block surface |
| `--color-doc-template-ink` | `#1a1a1a` | Primary text |
| `--color-doc-template-muted` | `#6b6b6b` | Secondary text |
| `--color-doc-template-faint` | `rgb(0 0 0 / 0.08)` | Hairline borders |
| `--color-doc-template-faint-strong` | `rgb(0 0 0 / 0.16)` | Stronger borders |
| `--color-doc-template-chip` | `rgb(0 0 0 / 0.04)` | Chip fills |
| `--color-doc-template-search` | `#f5f4f0` | Search field fill |
| `--color-doc-template-selected` | `#d9e6f7` | Active sidebar link |
| `--color-doc-template-accent` | `#23aaff` | Links, active TOC, highlights |
| `--color-doc-template-accent-soft` | `rgb(35 170 255 / 0.09)` | Accent washes |
| `--color-doc-template-warn` | `#d68b3a` | Warnings, dev-mode dot |
| `--color-doc-template-warn-soft` | `rgb(214 139 58 / 0.10)` | Warning washes |
| `--color-doc-template-rail-stroke` | `#d8d4c8` | TOC rail stroke |

Radii: `--radius-doc-template` (10px) and `--radius-doc-template-sm`
(6px).

## Dark mode

A `[data-theme='dark']` block re-declares the same custom properties —
every utility and inline style swaps instantly, with zero re-render.
The attribute is stamped on `<html>` by:

1. the FOUC-safe inline script in the server-rendered head (first
   paint), and
2. `ThemeProvider` afterwards (user choice, `system` tracking,
   cross-tab sync) — see [ThemeToggle](#themetoggle) below.

A `dark:` Tailwind variant is wired to the same attribute for your own
pages. Shiki code colors are dual-theme (`github-light` /
`github-dark`) via CSS variables, so code follows along.

## Fonts

| Token | Stack | Used for |
| --- | --- | --- |
| `--font-doc-template-ui` | Inter Tight, ui-sans-serif, … | Body, headings, links |
| `--font-doc-template-mono` | Fira Code, ui-monospace, … | Code, chips, section labels, breadcrumbs |

The server renderer emits a single Google Fonts `<link>` loading Inter
Tight + **Fira Code** (the org-wide mono font).

## Re-branding walkthrough

Restyling the shell is a stanza in your `app.css`, **after** the
library import. Override the light values on `:root` and the dark
values on `[data-theme='dark']`:

```css file="styles/app.css"
@import "tailwindcss";
@import "@dreamlake/dockit/styles.css";

@source "../pages";
@source "../renderer";

/* Brand: violet accent, tighter radii, your own type. */
:root {
  --color-doc-template-accent: #7c5cff;
  --color-doc-template-accent-soft: rgb(124 92 255 / 0.09);
  --color-doc-template-selected: #e6dffb;
  --radius-doc-template: 6px;
  --font-doc-template-ui: "Instrument Sans", ui-sans-serif, sans-serif;
}

[data-theme='dark'] {
  --color-doc-template-accent: #a78bff;
  --color-doc-template-selected: #423a5c;
}
```

Every component — sidebar selection, TOC highlight, links,
[chips](/reference/components/chip.md) — re-resolves through the variables; there
is nothing else to touch. If
you swap a font stack, remember to also load the webfont (the
renderer's default `<link>` only fetches Inter Tight and Fira Code).

## ThemeToggle

The theme control comes in two variants, chosen by the `themeToggle`
field in [Site config](/guides/site-config.md):

- **`'cycle'`** (default) — a single button that steps
  **light → system → dark**, with a small spring animation on the
  icon swap.
- **`'segmented'`** — the three-button slider from the uikit/dreamlake
  docs: every theme visible at once, a pill indicator sliding under
  the active one, inactive icons leaning ±18°.

```ts file="site.config.ts"
initDocs({
  site: { themeToggle: 'segmented' /* or 'cycle' */ },
  // …
})
```

This site is configured with `'segmented'`, so that is what the topbar
shows. Here is another toggle, live — both stay in sync because they
share the same `ThemeProvider` state:

`` takes no props — the variant is site-level config,
not a per-instance choice. It must render inside the shell (or any
`ThemeProvider`) — on its own it falls back to inert context defaults.

### How theme state flows

1. `ThemeProvider` (mounted by the [Layout](/reference/components/shell.md)) keeps
   the choice in localStorage under `doc:theme`, synced across tabs
   and across every consumer of the hook.
2. The provider stamps `data-theme="light" | "dark"` on `<html>`;
   every color token above re-resolves instantly.
3. On first paint, a tiny inline script in the server-rendered
   `<head>` reads the same key **before** hydration, so a dark-mode
   reader never sees a light flash (FOUC-safe).

### Reading the theme yourself

```tsx

function MyWidget() {
  const { theme, setTheme } = useTheme()   // 'light' | 'dark' | 'system'
  return <button onClick={() => setTheme('dark')}>Go dark ({theme})</button>
}
```
