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.
Exact Configuration
-
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); } -
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); } -
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
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
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 |
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.