Quick answer: MDX components make docs interactive and expressive, but they are custom code that only builds inside the component ecosystem that defines them, which is a real portability cost. Plain Markdown renders anywhere and stays portable, while MDX is best reserved for a small set of components that genuinely need interactivity. Keep the majority of content in plain Markdown, isolate the custom components, and ask whether each one earns the lock-in it creates or whether a standard element would do.

What MDX adds and what it costs

MDX lets you drop a component directly into a document, so a page can render a callout, a tabbed code sample, or a generated API block without leaving the content file. That is genuinely useful, but the component is not part of the Markdown standard. Move the same file to a different builder, a static site generator, or a plain Markdown renderer, and the custom tag either fails to build or renders as an unknown element. Every custom component is therefore a dependency that couples the content to a specific toolchain.

AspectPlain MarkdownMDX components
PortabilityRenders anywhereTied to the component library
ExpressivenessStandard elements onlyInteractive, custom layouts
Build complexityMinimalRequires a build and dependencies
Migration costLowHigh as components accumulate

Where the lock-in quietly grows

The trap is not one component; it is the accumulation. A single callout is easy to replace, but after a year of writing, dozens of pages can depend on a proprietary set of tags, and a migration means rewriting each one rather than moving a folder. The component names also tend to absorb content decisions, so an upgrade that changes a component's behavior silently changes every page that uses it, and the content no longer renders independently of the platform.

A component worth keeping

Not every custom element is a bad trade. A tabbed code sample that lets a reader pick a language is a genuine interaction that plain Markdown cannot express, so it can justify itself. The discipline is to keep such components few, generic, and clearly separated from the prose, so the content around them remains portable even if the component is later replaced.

// A justified, isolated interactive component

  
    ```js
    const res = await fetch("/api/projects");
    ```
  
  
    ```python
    res = requests.get("/api/projects")
    ```
  

Prefer standard elements by default

Before reaching for a component, check whether a standard Markdown element already covers the need. A warning that uses a custom callout often works just as well as a blockquote with a strong label, and a static list rarely needs a bespoke tag. Writing the default in plain Markdown keeps the content buildable in any tool and confines the custom surface to the few places that truly need it, which keeps the migration path open.

Keeping a migration path open

Even when you commit to MDX, treat the components as a thin layer with stable, generic names, and document the mapping from each component to an equivalent standard element. If you ever move platforms, that mapping lets you transform the tags mechanically rather than rewriting prose. Avoid building deep logic into a component that appears in content; the more it does, the more it becomes a framework your docs cannot leave.

How an agent mishandles it

An agent that is asked to make docs richer often wraps ordinary content in custom tags because it is the fastest way to produce a polished look, even when a standard element would work. Ask it to justify each component against a plain-Markdown alternative and keep the prose in standard syntax. If the agent introduces a new component for a one-off visual, reject that: proliferating single-use tags is exactly how the lock-in becomes unmanageable.

A worked plain-Markdown alternative

It is worth seeing how often a custom component is simply restating something the standard syntax already handles. A callout that exists to mark a warning can be written as a blockquote with a clear label, and a structured set of instructions works as an ordinary list. The rendered result is slightly less bespoke, but it builds in every Markdown tool and carries no dependency on a component library.


Do not call this endpoint from a browser; the token
will be visible in the request.


> **Warning:** Do not call this endpoint from a browser. The
> token is visible in the request.

Reserve the component for the cases where plain syntax genuinely cannot express the interaction, such as a live selector or generated block, and treat everything else as prose that should remain portable. Over time, this default keeps the custom surface small enough that a migration stays a mechanical job.

Auditing the component inventory

Periodically list every custom tag actually used in the docs and count how many pages depend on each. A component that appears only once is a candidate to replace with standard Markdown, while a component used widely should be the one with the clearest documented mapping. The inventory makes the lock-in visible as a concrete number rather than a vague worry, and it gives you a way to track whether the custom surface is growing or staying contained.

# List the custom tags used across the docs
grep -rhoE "<[A-Z][A-Za-z]+" docs | sort | uniq -c | sort -rn

Frequently asked questions

Is MDX bad for documentation?

No. It is valuable for genuine interactivity, but custom tags are dependencies that only build in their own ecosystem, so use them sparingly and keep the rest in plain Markdown.

What makes MDX lock-in risky?

Components accumulate across many pages, so switching platforms eventually means rewriting every custom tag rather than moving a folder of portable Markdown.

When is a custom component justified?

When it provides interaction that plain Markdown cannot express, such as a language tab selector, and it is kept generic, isolated, and documented.

How do I keep a future migration cheap?

Keep components few and stable, avoid putting logic inside them, and document a mapping from each tag to a standard element so it can be transformed mechanically.

This is part of the Powerduck vs Mintlify: API Documentation in the AI Era — start there for the full comparison of documentation platforms and their tradeoffs.