Design System
zudo-doc's tight token strategy — spacing, typography, colors, and more.
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:
@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/ 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:
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)" },
},
},
}),
);: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.
<!-- 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>Tip
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 - | 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 |
<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 |
<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] |
<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 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/ 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
Use registered tokens
Custom properties supply values; wind configuration registers utility names. Check the generated CSS whenever you add a token or an authored class.
Do
<!-- 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
<!-- 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/; packages/ is their source of truth. The scaffold's src/ 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):
<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):
<!-- 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):
<!-- 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/, src/, packages/.
For the broader reasoning (light-mode / dark-mode contrast, when an underline is sufficient vs when a color shift is also needed), consult the / skill locally — its light-mode/dark-mode and three-tier token strategy sections cover the trade-offs.