White Papers

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:

VariablePurpose
--color-fd-backgroundPage background
--color-fd-foregroundBody text
--color-fd-primaryAccent — TOC active state, links on hover
--color-fd-primary-foregroundText on primary backgrounds
--color-fd-mutedSecondary surfaces (sidebar, code blocks)
--color-fd-muted-foregroundSecondary text (descriptions, captions)
--color-fd-borderAll borders and dividers
--color-fd-cardCard and callout backgrounds
--color-fd-accentHover state for nav items

Override any of them in app/globals.css:

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:

lib/layout.shared.ts
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.

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:

content/docs/meta.json
{
  "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:

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:

app/layout.tsx
<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?

On this page