---
title: "Creating Your Own Theme"
description: "Step-by-step guide to building, publishing, and sharing custom themes"
canonical_url: "https://docs.farming-labs.dev/docs/themes/creating-themes"
markdown_url: "https://docs.farming-labs.dev/docs/themes/creating-themes.md"
last_updated: "2018-10-20"
agent:
  tokenBudget: 1200
  task: "Create, apply, and optionally publish a reusable Farming Labs docs theme."
  outcome: "The custom theme loads from docs.config, its CSS is present globally, and a production build renders the intended tokens and components."
  appliesTo:
    framework:
      - "nextjs"
      - "tanstackstart"
      - "sveltekit"
      - "astro"
      - "nuxt"
    version:
      - ">=0.2.60"
    package:
      - "@farming-labs/docs"
      - "@farming-labs/theme"
  prerequisites:
    - "Begin with a working docs application and identify the framework-specific global stylesheet."
    - "Decide whether to create a new theme, extend one preset, or override only a few theme values."
  files:
    - "themes/my-theme.ts"
    - "themes/my-theme.css"
    - "docs.config.tsx"
    - "src/lib/docs.config.ts"
    - "app/global.css"
    - "src/styles/app.css"
    - "src/app.css"
    - "nuxt.config.ts"
  sideEffects:
    - "Changing the active theme or its CSS affects layout and styling across the entire docs site."
    - "Publishing creates a versioned package consumed by other projects."
  verification:
    - description: "Run the project production build and inspect representative navigation, code, callout, table, and search components."
      expect: "The build succeeds with no missing CSS import and every component uses the intended theme tokens."
  rollback:
    - "Restore the previous theme factory and CSS import, then unpublish or deprecate an incorrect package version according to registry policy."
  failureModes:
    - symptom: "The theme configuration loads but pages look unstyled."
      resolution: "Import the theme's CSS from the framework global stylesheet and ensure its path matches the selected theme package."
    - symptom: "A shared theme works locally but fails for consumers."
      resolution: "Export the theme entry and CSS in package.json and test installation from the packed npm artifact."
---

# Creating Your Own Theme
URL: /docs/themes/creating-themes
LLM index: /llms.txt
Description: Step-by-step guide to building, publishing, and sharing custom themes
Related: /docs/themes, /docs/configuration, /docs/customization/components, /docs/customization/colors, /docs/customization/typography

<!-- farming-labs:agent-contract:start -->
## Agent Contract

Task: Create, apply, and optionally publish a reusable Farming Labs docs theme.
Outcome: The custom theme loads from docs.config, its CSS is present globally, and a production build renders the intended tokens and components.

### Applies To

- Framework: `nextjs`, `tanstackstart`, `sveltekit`, `astro`, `nuxt`
- Version: `>=0.2.60`
- Package: `@farming-labs/docs`, `@farming-labs/theme`

### Prerequisites

- Begin with a working docs application and identify the framework-specific global stylesheet.
- Decide whether to create a new theme, extend one preset, or override only a few theme values.

### Files

- `themes/my-theme.ts`
- `themes/my-theme.css`
- `docs.config.tsx`
- `src/lib/docs.config.ts`
- `app/global.css`
- `src/styles/app.css`
- `src/app.css`
- `nuxt.config.ts`

### Side Effects

- Changing the active theme or its CSS affects layout and styling across the entire docs site.
- Publishing creates a versioned package consumed by other projects.

### Verification

- Run the project production build and inspect representative navigation, code, callout, table, and search components.
  - Expected: The build succeeds with no missing CSS import and every component uses the intended theme tokens.

### Rollback

- Restore the previous theme factory and CSS import, then unpublish or deprecate an incorrect package version according to registry policy.

### Failure Modes

- The theme configuration loads but pages look unstyled. — Recovery: Import the theme's CSS from the framework global stylesheet and ensure its path matches the selected theme package.
- A shared theme works locally but fails for consumers. — Recovery: Export the theme entry and CSS in package.json and test installation from the packed npm artifact.
<!-- farming-labs:agent-contract:end -->

import { WebsiteThemePrompt } from "@/components/website-theme-prompt";

# Creating Your Own Theme

Build reusable presets with `createTheme` from `@farming-labs/docs`, give each one a unique `name`, and call its factory as `theme: myTheme()` inside `defineDocs()`. In contrast, pass the `DocsTheme` instance from `extendTheme()` as `theme: myTheme` without calling it.
Validate the production build plus navigation, search, code, callouts, and tables. Config-only themes need no custom CSS; when a package ships custom CSS, export it as `./css` and import it from the consumer's global stylesheet.
For an unstyled config-only theme, verify that the intended factory or extended instance reaches `defineDocs()`. For a package that only fails for consumers, verify `.` and, when present, `./css` in `package.json` exports against the packed artifact.

Everything starts with `createTheme()`. No CSS required — just a config object.

## Quick Start

```ts
import { createTheme } from "@farming-labs/docs";

export const myTheme = createTheme({
  name: "my-theme",
  ui: {
    colors: {
      primary: "#e11d48",
      background: "#09090b",
      muted: "#71717a",
      border: "#27272a",
    },
  },
});
```

```tsx title="docs.config.tsx"
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:

```tsx title="quick-start.tsx"
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.

<WebsiteThemePrompt>
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 markdown routes as implementation context before coding:
- Framework docs: https://docs.farming-labs.dev/docs.md
- Theme guide: https://docs.farming-labs.dev/docs/themes/creating-themes.md
- Components guide: https://docs.farming-labs.dev/docs/customization/components.md
- CLI guide: https://docs.farming-labs.dev/docs/cli.md

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/docs` theme as the base, or create a custom theme with
  `createTheme()` / `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 `.md` routes, `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
</WebsiteThemePrompt>

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.

```ts 
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).

```ts title="ui-colors.ts"
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.

```ts title="ui-typography.ts"
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`:

```ts title="example.ts"
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`.

```ts title="ui-radius.ts"
radius: "0.5rem"; // rounded
radius: "0px"; // sharp corners
radius: "0.75rem"; // more rounded
```

### `ui.layout`

Content area dimensions and structural options.

```ts title="ui-layout.ts"
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.

```ts title="ui-codeblock.ts"
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.

```ts title="ui-sidebar.ts"
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.

```ts title="ui-card.ts"
card: {
  bordered: true,          // show card borders
  background: undefined,   // override card background
}
```

### `ui.components`

Default props for built-in MDX components.

```ts title="ui-components.ts"
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:

```ts
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:

```ts
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:

```ts title="extending-an-existing-theme.ts"
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 a `DocsTheme` directly (not a factory). Use `createTheme()` when building a reusable preset.

---

## Cherry-Picking from Built-in Themes

Each built-in theme exports its defaults object:

```ts title="cherry-picking-from-built-in-themes.ts"
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

```text
my-docs-theme/
  src/
    index.ts       ← createTheme() + exported defaults
    theme.css      ← CSS overrides (optional)
  package.json
```

```json title="package.json"
{
  "name": "my-docs-theme",

## Publishing as an npm Package

```text
my-fumadocs-theme/
  src/
    index.ts       ← createTheme() + exported defaults
    theme.css      ← CSS overrides (optional)
  package.json
```

```json title="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:
```bash title="terminal"
npm install my-docs-theme
```

```tsx title="docs.config.tsx"
import { myTheme } from "my-docs-theme";
export default defineDocs({ entry: "docs", theme: myTheme() });
```

```css title="app/global.css"
@import "tailwindcss";
@import "my-docs-theme/css";
```
```bash title="terminal"
npm install my-fumadocs-theme
```

```tsx title="docs.config.tsx"
import { myTheme } from "my-fumadocs-theme";
export default defineDocs({ entry: "docs", theme: myTheme() });
```

```css title="app/global.css"
@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:

1. Imports a **color preset** (which includes fumadocs-ui core styles)
2. Overrides CSS variables for light (`:root`) and dark (`.dark`) modes
3. Targets specific component selectors to customize their appearance

### Starting Point

```css title="my-theme/theme.css"
/* 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

```css title="radius.css"
/* 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

```css title="example.css"
/* 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

```css title="inline-code.css"
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

```css title="tables.css"
.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

```css title="callouts.css"
[class*="bg-fd-card"],
[role="alert"] {
  border-radius: 0.5rem;
}

.dark [class*="bg-fd-card"] {
  border-color: hsl(0 0% 14%);
}
```

#### Tabs

```css title="tabs.css"
.dark [role="tablist"] {
  border-radius: 0.5rem;
}

.dark [role="tab"] {
  border-radius: 0.25rem;
}
```

#### Prev/Next Navigation Cards

```css title="prev-next-navigation-cards.css"
.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

```css title="search-dialog.css"
[role="dialog"] {
  border-radius: 0.5rem;
}
```

#### Breadcrumb

```css title="breadcrumb.css"
.fd-breadcrumb {
  font-size: 0.75rem;
}
.fd-breadcrumb-link {
  text-decoration: none;
}
.fd-breadcrumb-current {
  font-weight: 500;
}
```

#### Page Action Buttons

```css title="page-action-buttons.css"
.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

```css title="scrollbar.css"
.dark {
  --scrollbar-thumb: var(--color-fd-border);
  --scrollbar-thumb-hover: var(--color-fd-ring);
  --scrollbar-track: transparent;
}
```

#### Selection & Misc

```css title="selection-misc.css"
::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 `!important` sparingly** — fumadocs uses Tailwind utilities, so some overrides need `!important`. Check the built-in theme CSS files for examples.
- **Scope with `.dark`** — use `.dark` for 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-border` and `darksharp` CSS files are excellent references showing every component selector in use.

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
