Skip to content

Compatibility

This renderer isn't a drop-in replacement for react-markdown. It's a different package, but the props people usually migrate have the same names, the same meanings and the same default URL policy. Where the behaviour does differ, it's listed below.

The numbers below come from a test that renders identical input through both packages and compares normalized HTML. It regenerates docs/COMPATIBILITY.md on every run, so treat that file as the source of truth. This page summarizes it.

Result#

Measured against react-markdown@10.1.0. 39 comparisons across 11 props, and 39 produce identical markup.

StatusCount
Compatible11
Intentionally different1
Not supported yet1
Compatible with a documented change1

The 11 compatible props#

PropWhat was compared
childrenHeadings, emphasis, lists, code, quotes, links, hard breaks, empty string
componentsElement-name keys map to components, and the same props arrive
remarkPluginsTree transforms and dialect plugins, including remark-gfm
rehypePluginshast plugins running after mdast to hast and before the content policy
remarkRehypeOptionsFootnote label, back-label and clobber prefix
allowedElementsOnly listed tags survive, and children of a removed element go with it
disallowedElementsListed tags are removed and everything else stays
allowElementThe predicate receives element, index and parent
unwrapDisallowedA removed element is replaced by its children
urlTransformRuns on every URL attribute, with the same default algorithm
skipHtmlSame meaning and the same false default

Both packages default skipHtml to false, so raw HTML renders as visible escaped text. Neither executes raw HTML without an explicit rehype-raw opt-in.

The rows that differ#

className is intentionally different#

react-markdown 10 throws on a className prop. This renderer has no className prop either, and TypeScript rejects it, but at runtime it is ignored rather than thrown.

So JavaScript code that still passes className will quietly lose the wrapper instead of throwing. Use components or the opt-in classNames hooks described in styling instead.

MarkdownAsync and MarkdownHooks are not supported yet#

react-markdown 10 exports both for plugins that need async work. This renderer is synchronous only, so there's no equivalent export and an async remark or rehype plugin won't run.

remarkPlugins on a precompiled document has a documented change#

react-markdown only ever takes a string, so every plugin runs at parse time. This renderer also accepts an already-compiled MarkdownDocument.

A plugin that changes the dialect, such as remark-gfm, can't apply to text that was already parsed. Plugins that transform the tree still run. You'll want to compile with the same extensions you render with, or just pass the string.

// The plugin cannot add table syntax to text that is already a tree.
const document = compileMarkdown(source, { preset: gfmPreset })
<Markdown document={document} />

What is not covered#

Anything outside the prop surface doesn't have a react-markdown equivalent, so it isn't really a compatibility question. That includes bundle size, the MarkdownDocument input, presets, extensions, the editor and the variables plugin.

Migrating#

The migration guide walks through the codemod and the manual steps. Since the claims on this page are generated from tests, I'd check the matrix for your pinned version before planning a migration.

Last updated on