Theming Docusaurus with a Design System

A docs site that looks nothing like the rest of your product quietly tells developers it is a second-class citizen. Aligning Docusaurus with your company design system — the same colors, type scale, and components as your app — makes the docs feel like part of the platform rather than a bolt-on. This how-to wires design tokens into Docusaurus theming, swizzles components sparingly, and keeps light and dark modes honest. It extends Docusaurus Setup & Customization within Developer Portal Architecture & Frameworks.

Prerequisites

  • Docusaurus 3.x already building and serving your docs.
  • A design system that exposes tokens — colors, spacing, type scale — ideally as CSS custom properties or a published package.
  • Agreement on which brand elements are non-negotiable (primary color, font) versus which can stay Docusaurus defaults.
  • Familiarity with Docusaurus swizzling, since deep component changes go through it.
Prerequisites for design-system theming A working Docusaurus, exported tokens, an agreed brand scope, and swizzle familiarity enable theming. Docusaurus 3.x design tokens agreed brand scope swizzle familiarity On-brand docs light + dark
Tokens are the seam — map your design system to Docusaurus's Infima variables and most of the theming is done without touching components.

Exact Configuration

  1. Map design tokens onto Infima variables. Docusaurus themes through Infima CSS variables, so overriding them applies your palette everywhere at once.

    /* src/css/custom.css */
    :root {
      --ifm-color-primary: var(--brand-primary, #b25e78);
      --ifm-font-family-base: var(--brand-font, 'Inter', sans-serif);
      --ifm-code-font-size: 90%;
      --ifm-navbar-background-color: var(--brand-surface);
    }
    
  2. Define the dark palette explicitly so dark mode is designed, not merely inverted.

    /* src/css/custom.css */
    [data-theme='dark'] {
      --ifm-color-primary: var(--brand-primary-dark, #d98a79);
      --ifm-background-color: var(--brand-surface-dark, #14121a);
    }
    
  3. Swizzle sparingly — only components a token cannot reach, and prefer the safe wrapper form.

    # Requires @docusaurus/core >= 3.0
    npm run swizzle @docusaurus/theme-classic Footer -- --wrap
    
Tokens flow into the theme Design tokens map to Infima variables, which style all components; swizzling handles only what tokens cannot reach. design tokens Infima variables all components swizzle the exceptions
Reach for tokens first and swizzle last — every swizzled component is one you now maintain across Docusaurus upgrades.

Validation

# Requires a built site and node
# 1. The primary brand color is actually applied
npm run build && grep -r "ifm-color-primary" build/assets/css/ | head -1
# Expected: your brand value, not the Docusaurus green default

# 2. Dark mode defines its own palette (not just inverted)
grep -c "data-theme='dark'" src/css/custom.css
# Expected: >= 1 with real color overrides

# 3. Minimal swizzling — count ejected components
ls src/theme 2>/dev/null | wc -l
# Expected: a small number; each is future upgrade cost
Theming validation checks The brand color applies, dark mode is deliberately defined, and swizzling stays minimal. Brand applied not default green Dark designed own palette Swizzle minimal low upgrade cost
A low swizzle count is a health metric — the fewer ejected components, the smoother every future Docusaurus upgrade.

Edge Cases & Troubleshooting

Symptom Root Cause Resolution
Brand color ignored Override not in custom.css or wrong variable name Confirm the Infima variable name and that the file is loaded
Dark mode unreadable Only light palette defined; dark inverts it Author explicit [data-theme='dark'] colors
Theming breaks after upgrade Swizzled component drifted from upstream Re-swizzle or move to the --wrap form
Fonts flash then change Web font not preloaded Preload the brand font; set a matching fallback
Design system package out of sync Tokens pinned to an old version Track the design-system package version alongside docs
Theming pitfalls by class Ignored colors and unreadable dark mode are token issues; upgrade drift and font flashes are integration issues. Tokens color ignored dark unreadable → fix variable + palette Integration upgrade drift font flash → wrap + preload
Most theming pain is self-inflicted through over-swizzling; staying on tokens keeps the design system and docs upgradable in step.

Frequently Asked Questions

Swizzle or CSS override?

CSS override every time it can do the job. Swizzling ejects a component into your repo and freezes it against upstream, so each swizzle is a maintenance liability at every upgrade. Reserve it for structural changes — a custom navbar, a bespoke footer — that no variable can express, and prefer the --wrap form which keeps you closer to upstream.

How do I keep docs in sync with the app’s design system?

Consume the same token source both places — a published CSS or package that both the app and Docusaurus import — rather than copying values. When the design system ships a new primary color, both update together, and the docs never drift into looking like last year’s brand.