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" } },
});Prompt: Match an Existing Website
Use this prompt when you already have a product or marketing website and want the docs to feel like the same brand.
Create a docs theme from my website
Copy this into your coding agent when you want docs that match an existing site.
Create or update an @farming-labs/docs theme so the documentation feels visually consistent with
my existing website.
Inputs:
- Website URL: [WEBSITE_URL]
- Docs entry folder: [DOCS_ENTRY]
- Framework: [FRAMEWORK]
- Brandfetch brand context: [BRAND_CONTEXT]
Use the @farming-labs/docs website as implementation context before coding:
- Framework docs: https://docs.farming-labs.dev
- Theme guide: https://docs.farming-labs.dev/docs/themes/creating-themes
- Components guide: https://docs.farming-labs.dev/docs/customization/components
- CLI guide: https://docs.farming-labs.dev/docs/cli
Use the Brandfetch brand context as brand hints, then inspect the existing website directly and extract its design system:
- brand colors, accent colors, background colors, border colors, and focus states
- typography, font families, heading scale, body size, line height, and code font
- border radius, shadows, dividers, spacing rhythm, and card/button treatment
- navigation style, sidebar/header feel, search affordance, tabs, callouts, code blocks, and tables
- light/dark mode behavior if the site supports it
Then implement the docs theme:
- choose the closest built-in
@farming-labs/docstheme as the base, or create a custom theme withcreateTheme()/extendTheme() - update
docs.config.ts[x]to use the theme - add or update the theme CSS import in the app's global stylesheet
- add CSS overrides only where the config tokens are not enough
- keep the docs interface readable, scannable, and documentation-first; do not copy marketing hero sections into docs pages
- preserve agent-ready docs features such as
.mdroutes,llms.txt,AGENTS.md,skill.md, sitemap,robots.txt, MCP, JSON-LD, and markdown alternate links
Verification:
- run the docs dev server
- check desktop and mobile layouts
- confirm text does not overflow buttons, cards, sidebars, or code blocks
- confirm search, sidebar navigation, code blocks, callouts, tabs, and page actions still work
- list the files changed and explain which website design tokens were mapped into the docs theme
The copy button asks for the website URL, fetches Brandfetch brand details, defaults the docs entry
folder to docs, defaults the framework to Next.js, and fills those placeholders before copying.
If the website uses a very expressive landing page, ask for a docs interpretation of the brand
instead of a literal copy. Documentation should feel connected to the product while staying calmer,
denser, and easier to scan.
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" },
HoverLink: {
linkLabel: "Open page",
showIndicator: false,
align: "start",
},
}ui.components changes the default props for built-in MDX components. For example, HoverLink
can keep the built-in popover UI while changing its CTA label, indicator, or alignment across your
docs. If you want to replace the component completely, use components.HoverLink in
docs.config.tsx instead.
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" },
HoverLink: { linkLabel: "Open page", showIndicator: false },
},
};
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/theme";
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/theme/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/theme/default";
import { DarksharpUIDefaults } from "@farming-labs/theme/darksharp";
import { PixelBorderUIDefaults } from "@farming-labs/theme/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/theme/presets/black";
/* Or: @import "@farming-labs/theme/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/theme/presets/neutral | Light-first themes |
| Black | @farming-labs/theme/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.
How is this guide?