zudo-doc uses a **tight token strategy** with zfb's zudo-wind compiler. Authored CSS custom properties hold the values; the package preset registers the supported utility tokens. This reference targets the aligned zfb 4.3.3 family.

## Tight Token Strategy

The scaffold imports public package stylesheets and overrides values with ordinary CSS:

```css title="src/styles/global.css"
@layer zw-reset, zd-flow;
@import "@takazudo/zudo-doc/theme.css";
@import "@takazudo/zudo-doc/content.css";
@import "@takazudo/zudo-doc/page-loading.css";
@import "@takazudo/zudo-doc/features.css";

:root {
  --font-sans: "Inter", system-ui, sans-serif;
}
```

`theme.css` owns the custom properties and an authored `@layer base` reset patch. The preset selects `reset: "owned-v1"`; this is a zudo-wind reset plus the package patch, not identical Tailwind reset bytes. There are no Tailwind imports, `@source` directives, safelist stylesheet, or `@theme` token registration in this entry.



## What utility vocabulary you have

Use named project tokens such as `text-caption`, `bg-accent`, `px-hsp-md`, `w-icon-sm`, and `z-modal`. Structural utilities such as `flex`, `hidden`, and `grid-cols-3` and supported explicit lengths such as `m-[13px]` remain available. Not every Tailwind spelling is supported by zudo-wind.

The preset intentionally omits `spacingUnit`: nonzero numeric spacing such as `p-4` does not acquire an implicit quarter-rem scale. Use named spacing or explicit bracket lengths. Zero and `px` remain available. Default palette names such as `bg-blue-500` are not registered. Named group/peer variants, peer-checked, rings, and animations require authored CSS rather than an assumed utility.

Package classes are declared through the public `@takazudo/zudo-doc/wind.json` manifest. Project classes must be detectable by zfb's scanner. Add new semantic utility tokens through `zudoDoc({ wind })`; defining a custom property alone does not register a new utility:

```ts title="zfb.config.ts"
import { defineConfig } from "@takazudo/zfb/config";
import { zudoDoc } from "@takazudo/zudo-doc/config";

export default defineConfig(
  zudoDoc({
    siteName: "My Docs",
    wind: {
      tokens: {
        colors: { brand: "var(--project-brand)" },
      },
    },
  }),
);
```

```css title="src/styles/global.css (after package imports)"
:root {
  --project-brand: var(--zd-accent);
}
```

zfb deep-merges this override over the preset, retaining the other package tokens. `wind: false` explicitly disables generation. The theme continues to use `html[data-theme]` and custom properties with `dark: false`; it does not add a separate dark-utility theme.

Run `pnpm exec zfb wind audit --project-root . --fail-on error`. The flag makes error diagnostics fail the command. A clean audit does not prove every conditional class was discovered or every intended rule was emitted: also check `pnpm check:package-wind-manifest` in this repository and inspect generated CSS and computed values on representative pages.

## Spacing

zudo-doc separates spacing into two axes: **horizontal (hsp)** and **vertical (vsp)**. They serve different purposes in layout — horizontal spacing controls inline rhythm and gutters, while vertical spacing controls content flow and section separation. The vertical scale is stretched at larger sizes to give content more breathing room.

### Horizontal Spacing (hsp)

| Token | Value | Example Class |
|-------|-------|---------------|
| `hsp-2xs` | `0.125rem` (2px) | `px-hsp-2xs` |
| `hsp-xs` | `0.375rem` (6px) | `px-hsp-xs` |
| `hsp-sm` | `0.5rem` (8px) | `gap-x-hsp-sm` |
| `hsp-md` | `0.75rem` (12px) | `px-hsp-md` |
| `hsp-lg` | `1rem` (16px) | `px-hsp-lg` |
| `hsp-xl` | `1.5rem` (24px) | `px-hsp-xl` |
| `hsp-2xl` | `2rem` (32px) | `px-hsp-2xl` |

### Vertical Spacing (vsp)

| Token | Value | Example Class |
|-------|-------|---------------|
| `vsp-3xs` | `0.25rem` (4px) | `py-vsp-3xs` |
| `vsp-2xs` | `0.4375rem` (7px) | `py-vsp-2xs` |
| `vsp-xs` | `0.875rem` (14px) | `py-vsp-xs` |
| `vsp-sm` | `1.25rem` (20px) | `gap-y-vsp-sm` |
| `vsp-md` | `1.5rem` (24px) | `py-vsp-md` |
| `vsp-lg` | `1.75rem` (28px) | `py-vsp-lg` |
| `vsp-xl` | `2.5rem` (40px) | `py-vsp-xl` |
| `vsp-2xl` | `3.5rem` (56px) | `py-vsp-2xl` |

Both axes also include `0` (`0px`) and `px` (`1px`) utility values.

```html
<!-- Horizontal padding + vertical padding -->
<div class="px-hsp-lg py-vsp-md">Content with asymmetric spacing</div>

<!-- Grid with dual-axis gaps -->
<div class="grid gap-x-hsp-md gap-y-vsp-lg">Grid items</div>
```

[Dynamic content unavailable in text export]

Notice that `hsp-lg` is `1rem` while `vsp-lg` is `1.75rem` — the vertical axis is stretched at larger sizes. This is intentional — vertical flow needs more room than horizontal rhythm.



## Element Sizes

### Icon Sizes

| Token | Value | Example Class |
|-------|-------|---------------|
| `icon-xs` | `0.75rem` (12px) | `w-icon-xs h-icon-xs` |
| `icon-sm` | `1rem` (16px) | `w-icon-sm h-icon-sm` |
| `icon-md` | `1.25rem` (20px) | `w-icon-md h-icon-md` |
| `icon-lg` | `1.5rem` (24px) | `w-icon-lg h-icon-lg` |

## Elevation

Only one shadow token is defined.

| Token | Value | Example Class |
|-------|-------|---------------|
| `lg` | `0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)` | `shadow-lg` |

## Typography

### Font Sizes

Each semantic role references an abstract scale step (`--text-scale-*`); the
values below are the resolved sizes.

| Token | Value | Example |
|-------|-------|---------|
| `micro` | `0.75rem` / 12px | `text-micro` |
| `caption` | `0.875rem` / 14px | `text-caption` |
| `small` | `1rem` / 16px | `text-small` |
| `body` | `1.2rem` / 19.2px | `text-body` |
| `title` | `1.4rem` / 22.4px | `text-title` |
| `heading` | `3rem` / 48px | `text-heading` |
| `display` | `3.75rem` / 60px | `text-display` |

### Font Weights

| Token | Value | Example |
|-------|-------|---------|
| `normal` | `400` | `font-normal` |
| `medium` | `500` | `font-medium` |
| `semibold` | `600` | `font-semibold` |
| `bold` | `700` | `font-bold` |

### Line Heights

| Token | Value | Example |
|-------|-------|---------|
| `tight` | `1.25` | `leading-tight` |
| `snug` | `1.375` | `leading-snug` |
| `normal` | `1.5` | `leading-normal` |
| `relaxed` | `1.625` | `leading-relaxed` |

### Letter Spacing

Four steps from negative to open. Use `tracking-tight` on large headings to keep them optically tight; `tracking-normal` resets to the browser default; `tracking-wide`/`tracking-wider` suit labels and small-caps text.

| Token | Value | Example |
|-------|-------|---------|
| `tight` | `-0.025em` | `tracking-tight` |
| `normal` | `normal` | `tracking-normal` |
| `wide` | `0.05em` | `tracking-wide` |
| `wider` | `0.1em` | `tracking-wider` |

### Font Families

| Token | Stack | Example |
|-------|-------|---------|
| `sans` | System sans-serif stack | `font-sans` |
| `mono` | System monospace stack | `font-mono` |

```html
<h1 class="text-heading font-bold leading-tight">Page Title</h1>
<p class="text-body font-normal leading-normal">Body text</p>
<code class="text-small font-mono">inline code</code>
```

## Border Radius

| Token | Value | Example |
|-------|-------|---------|
| `DEFAULT` | `0.25rem` (4px) | `rounded` |
| `lg` | `0.5rem` (8px) | `rounded-lg` |
| `full` | `9999px` | `rounded-full` |

```html
<button class="rounded bg-accent text-bg">Default radius</button>
<div class="rounded-lg bg-surface">Card with larger radius</div>
<span class="rounded-full bg-muted">Pill badge</span>
```

## Breakpoints

| Token | Value | Example |
|-------|-------|---------|
| `sm` | `640px` | `sm:flex` |
| `lg` | `1024px` | `lg:grid-cols-2` |
| `xl` | `1280px` | `xl:max-w-[80rem]` |

```html
<div class="px-hsp-sm sm:px-hsp-md lg:px-hsp-lg xl:px-hsp-xl">
  Responsive horizontal padding
</div>
```

`max-sm`, `max-lg`, and `max-xl` use a strict width less than the configured pixel value. No `md` or `2xl` breakpoint is configured.

## Z-index Tiers

Use semantic z-index tiers instead of arbitrary stack values.

| Token | Value | Example Class |
|-------|-------|---------------|
| `content` | `0` | `z-content` |
| `local-1` | `1` | `z-local-1` |
| `local-2` | `2` | `z-local-2` |
| `local-3` | `3` | `z-local-3` |
| `sidebar` | `10` | `z-sidebar` |
| `toolbar` | `20` | `z-toolbar` |
| `dropdown` | `30` | `z-dropdown` |
| `popover` | `40` | `z-popover` |
| `modal-backdrop` | `50` | `z-modal-backdrop` |
| `modal` | `60` | `z-modal` |
| `toast` | `70` | `z-toast` |
| `tooltip` | `80` | `z-tooltip` |
| `drag` | `90` | `z-drag` |

## Colors

Colors use a **three-tier strategy**: raw palette values (Tier 1) flow into semantic tokens (Tier 2), which feed into component-scoped tokens (Tier 3). Each tier only references the tier above it, so swapping a color scheme updates the entire site at once.

See the [Color reference](https://zudo-doc.takazudomodular.com/docs/reference/color) for full details on the color token system, color schemes, and customization.

## Coexisting with another `@theme`

A host's separate Tailwind build may still use `@theme`, but zudo-doc's entry uses ordinary `:root` properties and zudo-wind token configuration. Keep the host namespace separate. zudo-doc reserves its 23 bare `--color-*` aliases and the `--color-zd-*` prefix; redefining a reserved property affects package chrome regardless of which compiler produced the stylesheet.

Import `@takazudo/zudo-doc/theme.css` before project overrides. There is no palette reset or `theme-no-reset.css` variant to choose. Existing `--color-zd-*` aliases remain available and are registered as wind color tokens; raw `--zd-*` properties belong to the color-scheme provider. Check emitted rules and the winning declaration when multiple stylesheets share a page.

## Usage Rules

[Dynamic content unavailable in text export]

Custom properties supply values; wind configuration registers utility names. Check the generated CSS whenever you add a token or an authored class.



### Do

```html
<!-- Semantic color tokens -->
<p class="text-fg">Primary text</p>
<div class="bg-surface border border-muted">Panel</div>
<a class="text-accent hover:text-accent-hover">Link</a>

<!-- Spacing tokens -->
<div class="px-hsp-lg py-vsp-md">Proper spacing</div>
<div class="gap-x-hsp-sm gap-y-vsp-md">Grid gaps</div>

<!-- Typography tokens -->
<h2 class="text-title font-semibold leading-tight">Heading</h2>
```

### Don't

```html
<!-- DON'T: Tailwind defaults — not defined and produce nothing -->
<div class="p-4 bg-gray-500 text-sm">Broken</div>

<!-- DON'T: Hardcoded hex — breaks theming -->
<div class="bg-[#1e1e2e] text-[#f8f8f2]">Breaks on theme switch</div>
```

Default tokens ship from `@takazudo/zudo-doc/theme.css`; `packages/zudo-doc/src/theme.css` is their source of truth. The scaffold's `src/styles/global.css` imports those defaults and provides the project override point.

## Interaction Rules

### Hover-state underline for link-like elements

Elements that _navigate_ (rendered as `<a href>` or behave as a link) must show an underline on hover and on keyboard focus. Buttons, toggles, and controls do not — they use a border/background change instead.

**Do** (navigational links):

```html
<a class="text-accent hover:underline focus-visible:underline">Link</a>
<a class="text-fg hover:text-accent hover:underline focus-visible:underline">Sidebar item</a>
```

**Don't** (missing focus-visible parity):

```html
<!-- DON'T: mouse users get an underline, keyboard users don't -->
<a class="text-fg hover:underline">Inaccessible to keyboard focus</a>
```

**Don't** (controls):

```html
<!-- DON'T: buttons use border/bg hover, not underline -->
<button class="hover:underline">Use hover:bg-accent/10 instead</button>
```

The pair `hover:underline focus-visible:underline` is the canonical form — always add both, never one without the other. Precedents: `packages/zudo-doc/src/header/header.tsx`, `src/components/site-tree-nav.tsx`, `packages/zudo-doc/src/footer/footer.tsx`.

For the broader reasoning (light-mode / dark-mode contrast, when an underline is sufficient vs when a color shift is also needed), consult the `/css-wisdom` skill locally — its light-mode/dark-mode and three-tier token strategy sections cover the trade-offs.