Components
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:
- Built-in components — headings, code blocks, callouts,
Tab,Tabs,HoverLink, etc. - Theme defaults — from
theme.ui.componentsfor built-ins likeHoverLinkandPrompt - 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
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
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:
# 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 elementsp,a,ul,ol,li— text and list elementstable,thead,tbody,tr,th,td— table elementspre,code— code blocks and inline codeimg— imagesblockquote— blockquoteshr— horizontal rulesAgent— optional shorthand for content that only appears in agent-projected outputHuman— shorthand for content that only appears in human-projected outputAudience— explicit audience wrapper withonly="human"oronly="agent"HoverLink— hover-triggered popover link card with title, description, and CTAPrompt— reusable AI prompt card with copy/open actionsTab,Tabs— tabbed contentCodeGroup— Mintlify-compatible tabbed code groups powered by the built-in code block UICallout— 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:
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 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.
<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.
<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:
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.
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",
},
},
},
}),
});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.
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"],
},
},
});<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 cardshowTitle— hide the title even iftitleis setshowDescription— hide the description even ifdescriptionis set
Live example:
Write agent-friendly docs
Give contributors a reusable authoring prompt.
If you want the prompt text visible on the page too, opt in:
<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:
Write agent-friendly docs
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
Example: Override the a tag
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>
);
}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
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>
);
}components: {
blockquote: CustomBlockquote,
},Example: Override pre (code blocks)
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>
);
}components: {
pre: CustomPre,
},Multiple Components
Register as many components as you need:
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:
# 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.
Override built-in or add custom MDX components
How is this guide?