Customization
Override colors, fonts, layout, and components — most changes are a single CSS variable away.
Fumadocs is designed to look reasonable out of the box and bend to your brand without a fork. Most customization happens in two files: app/globals.css for design tokens, and lib/layout.shared.ts for structural options.
Color tokens
Every color in the UI is driven by a CSS variable. The full set:
| Variable | Purpose |
|---|---|
--color-fd-background | Page background |
--color-fd-foreground | Body text |
--color-fd-primary | Accent — TOC active state, links on hover |
--color-fd-primary-foreground | Text on primary backgrounds |
--color-fd-muted | Secondary surfaces (sidebar, code blocks) |
--color-fd-muted-foreground | Secondary text (descriptions, captions) |
--color-fd-border | All borders and dividers |
--color-fd-card | Card and callout backgrounds |
--color-fd-accent | Hover state for nav items |
Override any of them in app/globals.css:
:root {
--brand-blue-300: #93c5fd;
--brand-sky-500: #0ea5e9;
--gradient-primary: linear-gradient(
to bottom,
var(--brand-blue-300),
var(--brand-sky-500)
);
--color-fd-primary: var(--brand-sky-500);
}OKLCH 101
oklch(L C H) — Lightness (0–1), Chroma (0–0.4ish), Hue (0–360°). L=0.5 reads
as the same brightness regardless of hue. For a light theme, set L between
0.85 and 0.95; for dark, 0.15 to 0.30.
Fonts
The site uses Geist by default. To swap, edit app/layout.tsx:
import { Geist, Geist_Mono } from "next/font/google";
const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"] });
const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"] });
import { Inter, JetBrains_Mono } from "next/font/google";
const sans = Inter({ variable: "--font-geist-sans", subsets: ["latin"] });
const mono = JetBrains_Mono({ variable: "--font-geist-mono", subsets: ["latin"] });Keep the variable names (--font-geist-sans, --font-geist-mono) — globals.css references them.
import localFont from "next/font/local";
const customSans = localFont({
src: "../public/fonts/MyFont.woff2",
variable: "--font-geist-sans",
});
Layout structure
lib/layout.shared.ts exposes the props passed into Fumadocs' DocsLayout:
export const baseOptions = {
nav: {
title: "nxt-whitepapers",
url: "/",
},
links: [
{ text: "Docs", url: "/docs" },
{ text: "GitHub", url: "https://github.com/your-org/nxt-whitepapers" },
],
githubUrl: "https://github.com/your-org/nxt-whitepapers",
};The links array shows in the top-right of the docs header. The githubUrl adds a small GitHub icon in the bottom-left of the sidebar.
Sidebar sections
Group pages by editing content/docs/meta.json. The pages array supports three entry types:
- A page slug —
"installation"→ renders the page in order - A folder name —
"coding-tutorials"→ renders the folder's contents (nested) - A separator —
"---Label---"→ renders a section heading
Example:
{
"pages": [
"index",
"ai-machine-learning",
"coding-tutorials",
"security",
"ui-ux",
"performances"
]
}Overriding the page component
If you need to add custom content above or below every page — a banner, an author bio, related links — edit app/docs/[[...slug]]/page.tsx:
<DocsBody>
<MDX components={{ ...defaultMdxComponents }} />
<RelatedPapers slug={page.slugs} />
<Feedback onSendAction={...} />
</DocsBody>Anything inside <DocsBody> inherits the typography styles. Anything outside it doesn't.
Don't fork the layout
It's tempting to copy DocsLayout from node_modules and modify it. Don't —
you lose upstream improvements and bug fixes. Override via props (sidebar,
nav, containerProps) instead. Fumadocs exposes most of what you'd want to
change.
Dark mode
Fumadocs ships with a theme toggle in the sidebar footer. The toggle writes to localStorage and adds a dark class to <html>. To force a default:
<RootProvider theme={{ defaultTheme: "dark", enableSystem: false }}>
{children}
</RootProvider>Setting enableSystem: false ignores the user's OS preference and uses defaultTheme for first-time visitors only.
How is this guide?