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 (from fumadocs-ui):
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 rulesHoverLink— hover-triggered popover link card with title, description, and CTAPrompt— reusable AI prompt card with copy/open actionsTab,Tabs— tabbed contentCallout— callout/admonition boxes
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,
providers: [
{
name: "ChatGPT",
urlTemplate: "https://chatgpt.com/?q=Read+this+documentation:+{url}",
promptUrlTemplate: "https://chatgpt.com/?q={prompt}",
},
{
name: "Claude",
urlTemplate: "https://claude.ai/new?q=Read+this+documentation:+{url}",
promptUrlTemplate: "https://claude.ai/new?q={prompt}",
},
{
name: "Cursor",
urlTemplate: "https://cursor.com/link/prompt?text=Read+this+documentation:+{url}",
promptUrlTemplate: "https://cursor.com/link/prompt?text={prompt}",
},
],
},
},
});<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.
How is this guide?