Home /

docs

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:

  1. Built-in components — headings, code blocks, callouts, Tab, Tabs, HoverLink, etc.
  2. Theme defaults — from theme.ui.components for built-ins like HoverLink
  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

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

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:

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 (from fumadocs-ui):

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.

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:

Theme defaults vs config overrides

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

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",
        },
      },
    },
  }),
});
docs.config.tsx
import { defineDocs } from "@farming-labs/docs";
import { MyHoverLink } from "./components/my-hover-link";

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

Example: Override the a tag

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>
  );
}
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

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>
  );
}
docs.config.tsx
components: {
  blockquote: CustomBlockquote,
},

Example: Override pre (code blocks)

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>
  );
}
docs.config.tsx
components: {
  pre: CustomPre,
},

Multiple Components

Register as many components as you need:

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:

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.