zudo-doc

Type to search...

to open search from anywhere

Color

CreatedMar 22, 2026Takeshi Takatsudo

zudo-doc's three-tier color strategy, palette system, color schemes, and customization.

zudo-doc uses a three-tier color strategy to keep every color on the site themeable. The default Tailwind theme is not imported, and the @theme block resets the color namespace with --color-*: initial before defining project tokens — only project tokens work. This ensures switching a color scheme updates the entire site at once.

Three-Tier Color Strategy

Colors are organized into three tiers. Each tier only references the tier above it:

TierNamePurposeDefined In
1PaletteRaw color values from the active color schemeColorSchemeProvider:root
2SemanticDesign meaning — what each color representssrc/styles/global.css @theme
3ComponentScoped overrides for specific components.zd-content in global.css

This layering means you can:

  • Swap the palette (change color scheme) → entire site updates
  • Remap a semantic token (e.g. make accent blue instead of cyan) → every component using accent updates
  • Override a component token without affecting other components

Tier 1: The 16-Color Palette

zudo-doc’s color system uses a 16-color palette. Each color scheme provides values for 16 indexed color slots plus background, foreground, and selection colors.

Palette Index Convention

Every color scheme must follow this standard index mapping. This ensures components and semantic tokens work consistently across all themes:

IndexRoleDescription
p0Dark surfaceDeepest surface (code blocks, overlays)
p1DangerRed family — errors, destructive actions
p2SuccessGreen family — confirmations, tips
p3WarningYellow/amber — caution messages
p4InfoBlue family — informational highlights
p5AccentPrimary interactive color (links, CTA)
p6NeutralSlate/cyan — borders, secondary elements
p7SecondaryMuted accent or secondary neutral
p8MutedGray — borders, secondary text, comments
p9BackgroundPage background
p10SurfaceElevated surface (panels, sidebars)
p11Text primaryMain body text
p12Accent variantBrighter or alternate accent
p13DecorativePurple/lavender — non-semantic decoration
p14Accent hoverHover state for interactive elements
p15Text secondarySecondary text or muted foreground

📝 Note

The actual hex values differ between light and dark schemes, but the role of each index stays the same. For example, p1 is always a danger/red color — #dd3131 in light mode, #da6871 in dark mode. This consistency is what makes it safe to use palette tokens directly and easy to create new schemes.

How Palette Colors Are Injected

The ColorSchemeProvider component (src/components/color-scheme-provider.astro) reads the active color scheme and injects CSS custom properties on :root at build time:

:root {
  --zd-bg: #282a36;
  --zd-fg: #f8f8f2;
  --zd-cursor: #f8f8f2;
  --zd-sel-bg: #44475a;
  --zd-sel-fg: #ffffff;
  --zd-0: #21222c;   /* palette slot 0 (Black) */
  --zd-1: #ff5555;   /* palette slot 1 (Red) */
  /* ... through --zd-15 */
}

These --zd-* properties are the source of truth. Everything downstream — semantic tokens, component tokens, Tailwind utilities — resolves back to them.

Tier 2: Semantic Tokens

In src/styles/global.css, the @theme block maps palette properties into Tailwind-compatible tokens with design meaning:

@theme {
  --color-*: initial;  /* reset ALL Tailwind defaults */

  /* Base */
  --color-bg: var(--zd-bg);
  --color-fg: var(--zd-fg);
  --color-sel-bg: var(--zd-sel-bg);
  --color-sel-fg: var(--zd-sel-fg);

  /* Raw palette access (p0–p15) */
  --color-p0: var(--zd-0);
  --color-p1: var(--zd-1);
  /* ... through --color-p15 */

  /* Semantic aliases */
  --color-surface: var(--zd-surface);
  --color-muted: var(--zd-muted);
  --color-accent: var(--zd-accent);
  --color-accent-hover: var(--zd-accent-hover);
  --color-code-bg: var(--zd-code-bg);
  --color-code-fg: var(--zd-code-fg);
  --color-success: var(--zd-success);
  --color-danger: var(--zd-danger);
  --color-warning: var(--zd-warning);
  --color-info: var(--zd-info);
}

Once registered in @theme, these become standard Tailwind utility classes: bg-surface, text-accent, border-muted, etc.

Semantic Token Reference

TokenDefault Palette SlotUsage
bg--zd-bg (p9)Page background
fg--zd-fg (p11)Primary text
surfacep0 (Dark surface)Panel/sidebar surfaces
mutedp8 (Muted)Muted text, borders, comments
accentp5 (Accent)Links, active states, CTA
accent-hoverp14 (Accent hover)Hover state for accent
sel-bg--zd-sel-bgSelection background
sel-fg--zd-sel-fgSelection foreground
code-bgp10 (Surface)Code block background
code-fgp11 (Text primary)Inline code text
successp2 (Success)Success states, confirmations
dangerp1 (Danger)Errors, destructive actions
warningp3 (Warning)Warning messages
infop4 (Info)Informational highlights

Per-Scheme Semantic Overrides

Each color scheme can override the default palette-slot mapping via the semantic property in src/config/color-schemes.ts. Values can be either a palette index (number) or a direct color string:

"My Custom Scheme": {
  // ...palette, background, foreground...
  semantic: {
    accent: 6,              // use palette[6] — no color duplication
    accentHover: 14,         // use palette[14]
    surface: "#f4efdd",      // direct color string (not in palette)
  },
},

Using palette indices instead of repeating color strings eliminates duplication and ensures semantic colors stay in sync when palette colors change (e.g., via the Color Tweak Panel).

The same number | string type (called ColorRef) is also supported for cursor, selectionBg, and selectionFg:

"My Theme": {
  background: "#1a1a2e",
  foreground: "#e0e0e0",
  cursor: 6,            // use palette[6] instead of duplicating the color
  selectionBg: "#3a3a5e",
  selectionFg: 15,       // use palette[15]
  palette: [...],
  shikiTheme: "one-dark-pro",
},

The resolution logic lives in src/config/color-scheme-utils.ts — each property falls back to its default palette slot when not explicitly set.

Tier 3: Component Tokens

Some components define their own color variables that consume Tier 2 semantic tokens. These are internal implementation details.

Content Typography

The .zd-content class provides direct element styling using semantic tokens — no external typography plugin:

.zd-content {
  color: var(--color-fg);
  font-size: var(--text-body);
  line-height: var(--leading-relaxed);
}

.zd-content :where(a) {
  color: var(--color-accent);
}

.zd-content :where(code:not(pre code)) {
  color: var(--color-code-fg);
  background-color: var(--color-code-bg);
}

.zd-content :where(li::marker) {
  color: var(--color-muted);
}
/* ... */

ℹ️ Info

Tier 3 tokens are internal to their components. When building your own UI, use Tier 2 semantic tokens or Tier 1 palette tokens directly.

Using Color Tokens

Prefer semantic tokens

Use semantic tokens for standard UI patterns:

<!-- Text -->
<p class="text-fg">Primary text</p>
<p class="text-muted">Secondary text</p>
<a class="text-accent hover:text-accent-hover">Link</a>

<!-- Backgrounds -->
<div class="bg-bg">Page background</div>
<div class="bg-surface">Panel or sidebar</div>

<!-- Borders -->
<div class="border border-muted">Bordered element</div>

Fall back to palette tokens when needed

Use p0p15 when no semantic token fits — for badges, decorative elements, or status indicators:

<span class="text-p1">Error text (red)</span>
<span class="text-p2">Success text (green)</span>
<span class="text-p3">Warning text (yellow)</span>
<span class="text-p4">Info text (blue)</span>

Color Schemes

To change the active color scheme, edit src/config/settings.ts:

export const settings = {
  colorScheme: "Default Dark",  // Change this
  siteName: "zudo-doc",
  // ...
};

Default Themes

zudo-doc includes a light and dark default theme. See src/config/color-schemes.ts for available schemes.

Adding a Custom Color Scheme

Add a new entry to the colorSchemes object in src/config/color-schemes.ts:

"My Theme": {
  background: 9,
  foreground: 11,
  cursor: 6,
  selectionBg: 10,
  selectionFg: 11,
  palette: [
    "#16213e", "#e74c3c", "#2ecc71", "#f39c12",  // p0-3: dark surface, danger, success, warning
    "#3498db", "#9b59b6", "#1abc9c", "#ecf0f1",  // p4-7: info, accent, neutral, secondary
    "#2c3e50", "#1a1a2e", "#2a2a4e", "#e0e0e0",  // p8-11: muted, background, surface, text
    "#5dade2", "#bb8fce", "#48c9b0", "#c0c0c0",  // p12-15: accent variant, decorative, hover, text secondary
  ],
  shikiTheme: "one-dark-pro",
  // Optional: override semantic defaults
  semantic: {
    accent: 12,       // use p12 as accent instead of default p5
    surface: "#1f1f3a", // direct color string also works
  },
},

The ColorScheme interface requires:

  • background, foregroundColorRef (palette index or color string, typically p9 and p11)
  • cursor, selectionBg, selectionFgColorRef (palette index or color string)
  • palette — 16-element tuple (color slots 0–15)
  • shikiTheme — a Shiki theme name for syntax highlighting
  • semantic (optional) — override default palette-slot mappings using ColorRef values (palette index or color string)

What NOT to Do

🚨 Color Anti-Patterns

Don’t use Tailwind defaults — they are reset to initial:

<!-- WRONG -->
<div class="bg-gray-800 text-blue-500">No visible color</div>

<!-- RIGHT -->
<div class="bg-surface text-accent">Works correctly</div>

Don’t hardcode hex values — it breaks theming:

<!-- WRONG -->
<div class="bg-[#1e1e2e]">Breaks on theme switch</div>

<!-- RIGHT -->
<div class="bg-p0">Adapts to any theme</div>

Don’t reference component-scoped variables in your own components:

/* WRONG — don't use hardcoded colors */
.my-component {
  color: #3b82f6;
}

/* RIGHT — use semantic tokens */
.my-component {
  color: var(--color-accent);
}

Revision History

AI Assistant

Ask a question about the documentation.