---
title: "Components"
description: "Override built-in or add custom MDX components"
canonical_url: "https://docs.farming-labs.dev/docs/customization/components"
markdown_url: "https://docs.farming-labs.dev/docs/customization/components.md"
last_updated: "2018-10-20"
agent:
  tokenBudget: 1000
  task: "Register a custom MDX component or override a built-in component without breaking page rendering."
  outcome: "The component is available in MDX, receives the intended props, and renders in the configured docs theme."
  appliesTo:
    framework:
      - "nextjs"
      - "tanstackstart"
      - "sveltekit"
      - "astro"
      - "nuxt"
    version:
      - ">=0.2.60"
    package:
      - "@farming-labs/docs"
  prerequisites:
    - "The project has a working docs.config file and an existing docs page for verification."
    - "Use docs.config.tsx when registration includes JSX."
    - "Confirm whether the requirement needs a config component override or a theme default-prop override."
  files:
    - "components/info-card.tsx"
    - "docs.config.tsx"
    - "src/lib/docs.config.ts"
    - "app/docs/getting-started/page.mdx"
    - "docs/getting-started/page.mdx"
  sideEffects:
    - "Overriding a built-in component changes every docs page that renders that MDX element."
  verification:
    - description: "Build the docs app and render an MDX page that uses the component."
      expect: "The build succeeds, the component renders once with the documented props, and existing built-ins still work."
  rollback:
    - "Remove the component registration and restore the previous built-in or theme defaults."
  failureModes:
    - symptom: "MDX reports that the custom component is undefined."
      resolution: "Export the component, register the exact case-sensitive name in docs.config.tsx, and restart the dev server."
    - symptom: "The config fails to parse after adding a component."
      resolution: "Rename docs.config.ts to docs.config.tsx when the config contains JSX."
---

# Components
URL: /docs/customization/components
LLM index: /llms.txt
Description: Override built-in or add custom MDX components
Related: /docs/configuration, /docs/customization/agent-primitive, /docs/customization/page-actions, /docs/themes/creating-themes

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

Task: Register a custom MDX component or override a built-in component without breaking page rendering.
Outcome: The component is available in MDX, receives the intended props, and renders in the configured docs theme.

### Applies To

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

### Prerequisites

- The project has a working docs.config file and an existing docs page for verification.
- Use docs.config.tsx when registration includes JSX.
- Confirm whether the requirement needs a config component override or a theme default-prop override.

### Files

- `components/info-card.tsx`
- `docs.config.tsx`
- `src/lib/docs.config.ts`
- `app/docs/getting-started/page.mdx`
- `docs/getting-started/page.mdx`

### Side Effects

- Overriding a built-in component changes every docs page that renders that MDX element.

### Verification

- Build the docs app and render an MDX page that uses the component.
  - Expected: The build succeeds, the component renders once with the documented props, and existing built-ins still work.

### Rollback

- Remove the component registration and restore the previous built-in or theme defaults.

### Failure Modes

- MDX reports that the custom component is undefined. — Recovery: Export the component, register the exact case-sensitive name in docs.config.tsx, and restart the dev server.
- The config fails to parse after adding a component. — Recovery: Rename docs.config.ts to docs.config.tsx when the config contains JSX.
<!-- farming-labs:agent-contract:end -->

# Components

For Next.js or TanStack Start, create the React component and register its exact case-sensitive name
under top-level `components` in `docs.config.ts[x]`; MDX can then use that name without an import.
Astro, SvelteKit, and Nuxt use their adapter-native component registration instead. Use
`theme.ui.components` only to change default props for a built-in; a top-level entry such as
`components.HoverLink` replaces that component across the React-rendered docs pages.

Build the docs app and render one MDX page with the documented props while confirming existing
built-ins still work. If MDX reports an undefined component, check its export and registration name
and restart the dev server. If JSX makes the config fail to parse, rename `docs.config.ts` to
`docs.config.tsx`. Remove a built-in override to restore the previous component; when removing a
custom component, remove its MDX usages too.

Pass custom React components that become available in **all MDX files** — no imports needed.

## How It Works

When you register components in `docs.config.ts`, they're merged into the MDX component map via `getMDXComponents()`. The merge order is:

1. **Built-in components** — headings, code blocks, callouts, `Tab`, `Tabs`, `HoverLink`, etc.
2. **Theme defaults** — from `theme.ui.components` for built-ins like `HoverLink` and `Prompt`
3. **Your components** — from `docs.config.ts` (overrides built-ins if names match)

This means you can both **add new components** and **override existing ones**.

---

## Creating a Custom Component

### 1. Create the component

```tsx title="components/info-card.tsx"
interface InfoCardProps {
  title: string;
  children: React.ReactNode;
  variant?: "default" | "tip" | "warning";
}

export function InfoCard({ title, children, variant = "default" }: InfoCardProps) {
  const colors = {
    default: "border-white/10 bg-white/[0.02]",
    tip: "border-emerald-500/20 bg-emerald-500/[0.04]",
    warning: "border-amber-500/20 bg-amber-500/[0.04]",
  };

  return (
    <div className={`border p-4 my-4 ${colors[variant]}`}>
      <p className="text-sm font-medium mb-1">{title}</p>
      <div className="text-sm text-fd-muted-foreground">{children}</div>
    </div>
  );
}
```

### 2. Register in config

```tsx title="docs.config.tsx"
import { defineDocs } from "@farming-labs/docs";
import { pixelBorder } from "@farming-labs/theme/pixel-border";
import { InfoCard } from "./components/info-card";

export default defineDocs({
  // ...theme, nav, etc.
  components: {
    InfoCard,
  },
});
```

### 3. Use in any MDX file

No import needed — just use it directly:

```mdx title="app/docs/getting-started/page.mdx"
# Getting Started

<InfoCard title="Quick Tip" variant="tip">
  You can use custom components anywhere without importing them.
</InfoCard>
```

---

## Overriding Built-in Components

You can replace any built-in MDX component by registering one with the **same name**. Your component will take precedence.

### Available built-in components

The following components are available by default through `getMDXComponents()`. You can use them in
MDX without imports or `docs.config.ts` registration:

- `h1`, `h2`, `h3`, `h4`, `h5`, `h6` — heading elements
- `p`, `a`, `ul`, `ol`, `li` — text and list elements
- `table`, `thead`, `tbody`, `tr`, `th`, `td` — table elements
- `pre`, `code` — code blocks and inline code
- `img` — images
- `blockquote` — blockquotes
- `hr` — horizontal rules
- `Agent` — optional shorthand for content that only appears in agent-projected output
- `Human` — shorthand for content that only appears in human-projected output
- `Audience` — explicit audience wrapper with `only="human"` or `only="agent"`
- `HoverLink` — hover-triggered popover link card with title, description, and CTA
- `Prompt` — reusable AI prompt card with copy/open actions
- `Tab`, `Tabs` — tabbed content
- `CodeGroup` — Mintlify-compatible tabbed code groups powered by the built-in code block UI
- `Callout` — callout/admonition boxes

### Example: Tailor content by audience

Audience wrappers are built in, so they do not need to be registered in `docs.config.ts`:

```mdx title="page.mdx"
Shared setup instructions.

<Human>Use the highlighted control in the screenshot.</Human>

<Agent>Run `pnpm verify` and require exit code 0.</Agent>

<Audience only="human">This is the explicit form of `Human`.</Audience>

<Audience only="agent">This is the explicit form of `Agent`.</Audience>
```

Rendered HTML and public search use the human projection. Markdown, Ask AI, MCP, `llms-full.txt`,
and static agent exports use the agent projection; compact `llms.txt` links point to that Markdown.
Sitemaps contain route metadata rather than page bodies. Content outside a wrapper is shared, and
`<Agent>` remains entirely optional. Keep `Audience.only` as a static literal and do not pass
audience props through a spread; `docs review` reports declarations that static outputs cannot
resolve consistently.

These wrappers are not authentication or authorization. Never place secrets in them. See
[Agent Primitive](/docs/customization/agent-primitive) for the complete surface matrix and
`agent.md` override behavior.

### Example: Use `CodeGroup`

`CodeGroup` lets you wrap multiple fenced code blocks in one tabbed block. The tab label comes from
`title`, `filename`, or the bare filename after the language, so Mintlify-style docs can move over
without custom components or MDX component registration.

````mdx title="page.mdx"
<CodeGroup>

```bash npm
npm install @farming-labs/docs
```

```bash pnpm
pnpm add @farming-labs/docs
```

```ts title="docs.config.ts"
import { defineDocs } from "@farming-labs/docs";

export default defineDocs({
  entry: "app/docs",
});
```

</CodeGroup>
````

Use `<CodeGroup dropdown>` when you want to preserve Mintlify-authored MDX that includes the
`dropdown` prop. It is accepted by the renderer and exposed as a data attribute for theme styling.

### Example: Use `HoverLink`

`HoverLink` gives you an inline trigger that opens a richer popover card on hover or focus. It is useful when you want to reference another page without breaking the reader's flow immediately.

```mdx title="page.mdx"
<HoverLink
  href="/docs/installation"
  title="Installation"
  description="Set up @farming-labs/docs in a new or existing app with the CLI or the manual steps."
  linkLabel="Read the guide"
>
  installation guide
</HoverLink>
```

Live example:

<HoverLink
  href="/docs/installation"
  title="Installation"
  description="Set up @farming-labs/docs in a new or existing app with the CLI or the manual steps."
  linkLabel="Read the guide"
>
  installation guide
</HoverLink>

### Theme defaults vs config overrides

Use `theme.ui.components` when you want to keep a built-in like `HoverLink` or `Prompt` but change its default props across the site. Use `components.HoverLink` or `components.Prompt` when you want to replace the component entirely.

```tsx title="docs.config.tsx"
import { defineDocs } from "@farming-labs/docs";
import { pixelBorder } from "@farming-labs/theme/pixel-border";

export default defineDocs({
  theme: pixelBorder({
    ui: {
      components: {
        HoverLink: {
          linkLabel: "Open guide",
          showIndicator: false,
          align: "start",
        },
      },
    },
  }),
});
```

```tsx title="docs.config.tsx"
import { defineDocs } from "@farming-labs/docs";
import { MyHoverLink } from "./components/my-hover-link";

export default defineDocs({
  components: {
    HoverLink: MyHoverLink,
  },
});
```

### Example: Use `Prompt`

`Prompt` turns a reusable AI prompt into a first-class docs component. By default, the prompt body is treated as payload for copy/open actions rather than visible page content, so the card stays compact like Mintlify-style prompt blocks.

```tsx title="docs.config.tsx"
import { ArrowUpRight, Check, Copy, Sparkles } from "lucide-react";
import { defineDocs } from "@farming-labs/docs";
import { pixelBorder } from "@farming-labs/theme/pixel-border";

export default defineDocs({
  icons: {
    sparkles: <Sparkles size={16} />,
    copy: <Copy size={16} />,
    check: <Check size={16} />,
    arrowUpRight: <ArrowUpRight size={16} />,
  },
  theme: pixelBorder({
    ui: {
      components: {
        Prompt: {
          icon: "sparkles",
          actions: ["copy", "open"],
          providers: ["ChatGPT", "Claude", "Cursor"],
          copyIcon: "copy",
          copiedIcon: "check",
          openIcon: "arrowUpRight",
        },
      },
    },
  }),
  pageActions: {
    openDocs: {
      enabled: true,
      target: "markdown",
      providers: ["chatgpt", "claude", "cursor"],
    },
  },
});
```

```mdx title="page.mdx"
<Prompt
  title="Write agent-friendly docs"
  description="Give contributors a reusable authoring prompt."
>
Write a concise agent-friendly documentation page for this feature.

Include:
- the user problem it solves
- the exact setup steps
- one verification checklist
- one troubleshooting section
</Prompt>
```

Useful visibility props:

- `showPrompt` — render the prompt body inside the card
- `showTitle` — hide the title even if `title` is set
- `showDescription` — hide the description even if `description` is set

Live example:

<Prompt
  title="Write agent-friendly docs"
  description="Give contributors a reusable authoring prompt."
>
Write a concise agent-friendly documentation page for this feature.

Include:
- the user problem it solves
- the exact setup steps
- one verification checklist
- one troubleshooting section
</Prompt>

If you want the prompt text visible on the page too, opt in:

```mdx title="page.mdx"
<Prompt
  title="Visible prompt body"
  description="Show the prompt on the page and still keep copy/open actions."
  showPrompt
>
Write a concise troubleshooting checklist for this feature.
</Prompt>
```
Live example: 
<Prompt
  title="Write agent-friendly docs"
  description="Give contributors a reusable authoring prompt."
  showPrompt
>
Write a concise agent-friendly documentation page for this feature.

Include:
- the user problem it solves
- the exact setup steps
- one verification checklist
- one troubleshooting section
</Prompt>

### Example: Override the `a` tag

```tsx title="components/custom-link.tsx"
import Link from "next/link";

export function CustomLink({
  href,
  children,
  ...props
}: React.AnchorHTMLAttributes<HTMLAnchorElement>) {
  const isExternal = href?.startsWith("http");

  if (isExternal) {
    return (
      <a href={href} target="_blank" rel="noopener noreferrer" {...props}>
        {children} ↗
      </a>
    );
  }

  return (
    <Link href={href || "#"} {...props}>
      {children}
    </Link>
  );
}
```

```tsx title="docs.config.tsx"
components: {
  a: CustomLink,
},
```

Now every link in your MDX files will use `CustomLink` — external links open in a new tab with an arrow indicator.

### Example: Override `blockquote`

```tsx title="components/custom-blockquote.tsx"
export function CustomBlockquote({ children }: { children: React.ReactNode }) {
  return (
    <blockquote className="border-l-2 border-white/20 pl-4 my-4 text-fd-muted-foreground italic">
      {children}
    </blockquote>
  );
}
```

```tsx title="docs.config.tsx"
components: {
  blockquote: CustomBlockquote,
},
```

### Example: Override `pre` (code blocks)

```tsx title="components/custom-code-block.tsx"
export function CustomPre({ children, ...props }: React.HTMLAttributes<HTMLPreElement>) {
  return (
    <div className="relative group my-4">
      <pre className="border border-white/10 bg-black p-4 overflow-x-auto" {...props}>
        {children}
      </pre>
    </div>
  );
}
```

```tsx title="docs.config.tsx"
components: {
  pre: CustomPre,
},
```

---

## Multiple Components

Register as many components as you need:

```tsx title="docs.config.tsx"
import { InfoCard } from "./components/info-card";
import { CustomLink } from "./components/custom-link";
import { VideoEmbed } from "./components/video-embed";
import { ApiReference } from "./components/api-reference";

export default defineDocs({
  // ...
  components: {
    // Custom components
    InfoCard,
    VideoEmbed,
    ApiReference,

    // Built-in overrides
    a: CustomLink,
  },
});
```

Then use them all across your MDX without imports:

```mdx title="page.mdx"
# API Overview

<InfoCard title="Authentication Required" variant="warning">
  All endpoints require a valid API key.
</InfoCard>

<ApiReference endpoint="/api/users" method="GET" />

<VideoEmbed url="https://youtube.com/watch?v=..." />
```

---

## Component Props

Components receive their props as defined in your implementation. The only requirement is that they must be valid React components (function or class).

For **built-in overrides**, your component should accept the same props as the original element (e.g., `React.AnchorHTMLAttributes` for `a`, `React.HTMLAttributes<HTMLPreElement>` for `pre`).

For **custom components**, define whatever props interface you need.

## 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).
