Creating Your Own Theme
Everything starts with createTheme(). No CSS required — just a config object.
Quick Start
import { createTheme } from "@farming-labs/docs";
export const myTheme = createTheme({
name: "my-theme",
ui: {
colors: {
primary: "#e11d48",
background: "#09090b",
muted: "#71717a",
border: "#27272a",
},
},
});import { defineDocs } from "@farming-labs/docs";
import { myTheme } from "./my-theme";
export default defineDocs({
entry: "docs",
theme: myTheme(),
});That's it. Users can override any of your defaults:
theme: myTheme({
ui: { colors: { primary: "#3b82f6" } },
})Config Options Reference
Here's everything you can configure in createTheme().
name
Unique identifier for your theme. Used for debugging and CSS scoping.
name: "my-theme"ui.colors
Color tokens mapped to --color-fd-* CSS variables at runtime. Any valid CSS color value works (hex, rgb, hsl, oklch).
colors: {
primary: "#e11d48", // brand color — links, buttons, active states
primaryForeground: "#ffffff", // text on primary backgrounds
background: "#09090b", // page background
foreground: "#fafafa", // default text color
muted: "#71717a", // muted backgrounds (badges, tags)
mutedForeground: "#a1a1aa", // secondary text, descriptions
border: "#27272a", // borders and dividers
card: "#18181b", // card / callout backgrounds
cardForeground: "#fafafa", // text inside cards
accent: "#27272a", // hover backgrounds
accentForeground: "#fafafa", // text on hover backgrounds
secondary: "#27272a", // secondary button backgrounds
secondaryForeground: "#fafafa", // text on secondary
popover: "#18181b", // dropdown / dialog backgrounds
popoverForeground: "#fafafa", // text inside popovers
ring: "#e11d48", // focus ring on interactive elements
}You only need to set the ones you want to change — the rest are inherited from the base preset.
ui.typography
Font families, heading sizes, weights, line heights, and letter spacing.
typography: {
font: {
// Font families
style: {
sans: "Inter, system-ui, sans-serif",
mono: "JetBrains Mono, monospace",
},
// Heading styles
h1: { size: "2.25rem", weight: 700, lineHeight: "1.2", letterSpacing: "-0.02em" },
h2: { size: "1.5rem", weight: 600, lineHeight: "1.3", letterSpacing: "-0.01em" },
h3: { size: "1.25rem", weight: 600, lineHeight: "1.4" },
h4: { size: "1.125rem", weight: 600, lineHeight: "1.4" },
// Body and small text
body: { size: "1rem", weight: 400, lineHeight: "1.75" },
small: { size: "0.875rem", weight: 400, lineHeight: "1.5" },
},
}Each heading/text style accepts these properties:
| Property | Type | Example |
|---|---|---|
size | string | "2.25rem", "36px" |
weight | number | string | 700, "bold" |
lineHeight | string | "1.2", "28px" |
letterSpacing | string | "-0.02em" |
Tip: Use CSS variables for fonts so users can load them with next/font:
style: {
sans: "var(--font-geist-sans, system-ui, sans-serif)",
mono: "var(--font-geist-mono, ui-monospace, monospace)",
}ui.radius
Global border radius. Maps to CSS --radius.
radius: "0.5rem" // rounded
radius: "0px" // sharp corners
radius: "0.75rem" // more roundedui.layout
Content area dimensions and structural options.
layout: {
contentWidth: 768, // max width of docs content (px)
sidebarWidth: 280, // sidebar width (px)
tocWidth: 220, // table of contents width (px)
toc: {
enabled: true, // show table of contents
depth: 3, // heading depth (2 = h2 only, 3 = h2 + h3)
},
header: {
height: 64, // header height (px)
sticky: true, // stick header on scroll
},
}ui.codeBlock
Code block rendering options.
codeBlock: {
showLineNumbers: false, // show line numbers
showCopyButton: true, // show copy button
theme: "github-dark", // shiki syntax theme
darkTheme: "github-dark", // dark mode shiki theme (for dual-theme)
}ui.sidebar
Sidebar visual style.
sidebar: {
style: "default", // "default" | "bordered" | "floating"
background: undefined, // override sidebar background
borderColor: undefined, // override sidebar border color
}| Style | Description |
|---|---|
"default" | Standard fumadocs sidebar |
"bordered" | Visible bordered sections (inspired by better-auth.com) |
"floating" | Floating card sidebar with subtle shadow |
ui.card
Card styling.
card: {
bordered: true, // show card borders
background: undefined, // override card background
}ui.components
Default props for built-in MDX components.
components: {
Callout: { variant: "outline" }, // or "soft"
CodeBlock: { showCopyButton: true },
Tabs: { style: "default" },
}Full Example
Here's a complete theme with every option:
import { createTheme } from "@farming-labs/docs";
export const MyThemeDefaults = {
colors: {
primary: "#8b5cf6",
background: "#0f0f11",
foreground: "#e4e4e7",
muted: "#1e1e2e",
mutedForeground: "#71717a",
border: "#27272a",
card: "#18181b",
accent: "#27272a",
ring: "#8b5cf6",
},
typography: {
font: {
style: {
sans: "Inter, system-ui, sans-serif",
mono: "Fira Code, monospace",
},
h1: { size: "2.5rem", weight: 800, letterSpacing: "-0.03em" },
h2: { size: "1.75rem", weight: 700, letterSpacing: "-0.02em" },
h3: { size: "1.25rem", weight: 600 },
body: { size: "1rem", lineHeight: "1.8" },
},
},
radius: "0.5rem",
layout: {
contentWidth: 800,
sidebarWidth: 260,
toc: { enabled: true, depth: 3 },
header: { height: 56, sticky: true },
},
sidebar: { style: "bordered" as const },
codeBlock: { showCopyButton: true, theme: "github-dark" },
components: {
Callout: { variant: "outline" },
},
};
export const myTheme = createTheme({
name: "my-theme",
ui: MyThemeDefaults,
});
// Export defaults so others can extend your theme
export { MyThemeDefaults };Extending an Existing Theme
Don't want to start from scratch? Use extendTheme() to build on top of a built-in preset:
import { extendTheme } from "@farming-labs/docs";
import { fumadocs } from "@farming-labs/fumadocs";
export const myTheme = extendTheme(fumadocs(), {
name: "my-fumadocs-variant",
ui: {
colors: { primary: "#22c55e", background: "#0c0c0c" },
sidebar: { style: "bordered" },
},
});Works with any built-in theme:
import { darksharp } from "@farming-labs/fumadocs/darksharp";
export const myDark = extendTheme(darksharp(), {
name: "my-dark-variant",
ui: {
colors: { primary: "#f97316" },
radius: "0.75rem",
},
});
extendTheme()returns aDocsThemedirectly (not a factory). UsecreateTheme()when building a reusable preset.
Cherry-Picking from Built-in Themes
Each built-in theme exports its defaults object:
import { DefaultUIDefaults } from "@farming-labs/fumadocs/default";
import { DarksharpUIDefaults } from "@farming-labs/fumadocs/darksharp";
import { PixelBorderUIDefaults } from "@farming-labs/fumadocs/pixel-border";
import { createTheme } from "@farming-labs/docs";
export const myTheme = createTheme({
name: "my-hybrid-theme",
ui: {
colors: PixelBorderUIDefaults.colors,
typography: DefaultUIDefaults.typography,
layout: DarksharpUIDefaults.layout,
sidebar: { style: "floating" },
},
});Publishing as an npm Package
my-fumadocs-theme/
src/
index.ts ← createTheme() + exported defaults
theme.css ← CSS overrides (optional)
package.json{
"name": "my-fumadocs-theme",
"version": "1.0.0",
"type": "module",
"exports": {
".": { "import": "./dist/index.mjs", "types": "./dist/index.d.mts" },
"./css": "./src/theme.css"
},
"peerDependencies": {
"@farming-labs/docs": ">=0.0.1"
}
}Users install and use:
npm install my-fumadocs-themeimport { myTheme } from "my-fumadocs-theme";
export default defineDocs({ entry: "docs", theme: myTheme() });@import "tailwindcss";
@import "my-fumadocs-theme/css";Advanced: CSS Customization
The config options above cover most use cases. But if you need pixel-level control over specific components — like how the sidebar active state looks, code block borders, table styling, or callout appearance — you can write a CSS file alongside your theme.
How it Works
Every theme can ship a CSS file that sits on top of the config. The CSS file:
- Imports a color preset (which includes fumadocs-ui core styles)
- Overrides CSS variables for light (
:root) and dark (.dark) modes - Targets specific component selectors to customize their appearance
Starting Point
/* Pick a color preset as your base */
@import "@farming-labs/fumadocs/presets/black";
/* Or: @import "@farming-labs/fumadocs/presets/neutral"; */
/* Override CSS variables */
.dark {
--color-fd-primary: #22d3ee;
--color-fd-background: #0a0a0f;
--color-fd-border: #1e293b;
--radius: 0.5rem;
}Two presets are available:
| Preset | Import | Best for |
|---|---|---|
| Neutral | @farming-labs/fumadocs/presets/neutral | Light-first themes |
| Black | @farming-labs/fumadocs/presets/black | Dark-first themes |
Component CSS Selectors
Here are all the selectors you can target to style individual components:
Sidebar
/* Container */
.dark aside#nd-sidebar {
background: hsl(0 0% 2%);
border-color: hsl(0 0% 12%);
}
/* All sidebar links */
.dark aside a[data-active] {
font-size: 0.875rem;
border-radius: 0.2rem;
color: hsl(0 0% 50%);
}
/* Active link (current page) */
.dark aside a[data-active="true"] {
background: hsl(0 0% 14%);
color: var(--color-fd-primary);
font-weight: 600;
}
/* Hover on inactive links */
.dark aside a[data-active="false"]:hover {
color: hsl(0 0% 90%);
}
/* Folder toggle buttons */
.dark aside button.text-fd-muted-foreground {
color: hsl(0 0% 90%);
font-weight: 500;
}
/* Child links inside folders */
.dark aside div.overflow-hidden[data-state] a[data-active] {
font-size: 0.785rem;
padding-left: 2rem;
}
/* Search button */
.dark aside button[class*="bg-fd-secondary"] {
background: transparent;
border-top: 1px solid hsl(0 0% 12%);
border-bottom: 1px solid hsl(0 0% 12%);
}
/* Active link indicator bar */
.dark aside a[data-active="true"]::before {
background-color: var(--color-fd-primary);
width: 2px;
}Code Blocks
/* Container */
figure.shiki {
border: 1px solid var(--color-fd-border);
border-radius: 0.5rem;
}
/* Title bar */
figure.shiki > div:first-child {
border-radius: 0.5rem 0.5rem 0 0;
}
/* Copy button */
figure.shiki button {
border-radius: 0.25rem;
}Inline Code
code:not(pre code) {
border-radius: 0.25rem;
}
.dark code:not(pre code) {
background: hsl(0 0% 10%);
border: 1px solid hsl(0 0% 16%);
color: hsl(0 0% 85%);
}Tables
.dark table {
border: 1px solid hsl(0 0% 15%);
}
.dark th {
background: hsl(0 0% 6%);
color: hsl(0 0% 80%);
}
.dark td {
border-color: hsl(0 0% 12%);
}Callouts
[class*="bg-fd-card"],
[role="alert"] {
border-radius: 0.5rem;
}
.dark [class*="bg-fd-card"] {
border-color: hsl(0 0% 14%);
}Tabs
.dark [role="tablist"] {
border-radius: 0.5rem;
}
.dark [role="tab"] {
border-radius: 0.25rem;
}Prev/Next Navigation Cards
.dark article a[class*="border"] {
border-color: hsl(0 0% 15%);
border-radius: 0.5rem;
}
.dark article a[class*="border"]:hover {
background: hsl(0 0% 6%);
}Search Dialog
[role="dialog"] {
border-radius: 0.5rem;
}Breadcrumb
.fd-breadcrumb { font-size: 0.75rem; }
.fd-breadcrumb-link { text-decoration: none; }
.fd-breadcrumb-current { font-weight: 500; }Page Action Buttons
.fd-page-action-btn {
border-radius: 0.375rem;
background: var(--color-fd-secondary);
border: 1px solid var(--color-fd-border);
}
.fd-page-action-menu {
border-radius: 0.5rem;
background: var(--color-fd-popover);
}Scrollbar
.dark {
--scrollbar-thumb: var(--color-fd-border);
--scrollbar-thumb-hover: var(--color-fd-ring);
--scrollbar-track: transparent;
}Selection & Misc
::selection {
background: var(--color-fd-foreground);
color: var(--color-fd-background);
}
.dark hr {
border-color: hsl(0 0% 15%);
}Tips
- Inspect in DevTools — the fastest way to find the right selector is to right-click an element, inspect it, and check its classes/attributes.
- Use
!importantsparingly — fumadocs uses Tailwind utilities, so some overrides need!important. Check the built-in theme CSS files for examples. - Scope with
.dark— use.darkfor dark mode styles. Fumadocs uses class-based dark mode. - Reference CSS variables — always use
var(--color-fd-*)in your styles so they respond to user color overrides. - Look at built-in themes — the
pixel-borderanddarksharpCSS files are excellent references showing every component selector in use.