React Markdown Kit vs react-markdown
Both turn a Markdown string into React elements, both parse with micromark, and both
refuse to use dangerouslySetInnerHTML. react-markdown is the one almost everyone
already has. This page compares them section by section, in the same order used on every
comparison page here: install size, GFM, security defaults, streaming, server rendering,
plugins, migration.
Facts about react-markdown come from its repository,
remarkjs/react-markdown, at version 10.1.0. Facts about the
kit link to the test or the generated table that produced them.
The short version: stay on react-markdown if it works and you care about the last
kilobyte. Look here if you stream Markdown from a model, render the same document more
than once, or want the editor and the plugins to read what the renderer reads. Read
the alternative page for the three
differences in full.
Install size
Bytes a browser downloads for one import, measured from published tarballs by
scripts/compare-bundles.mjs and
committed to
docs/data/bundle-sizes.json on
2026-09-20. React and React DOM are external in every row, minification is esbuild with
NODE_ENV set to production, and 1 KB is 1024 bytes.
| Import | Version | Minified | Gzipped | License | Peer react |
|---|---|---|---|---|---|
| react-markdown, CommonMark | 10.1.0 | 115.0 KB | 35.5 KB | MIT | >=18 |
| @react-markdown-kit/renderer, CommonMark | 0.1.0 | 118.5 KB | 36.8 KB | MIT | >=18 |
| react-markdown + remark-gfm | 10.1.0 | 152.6 KB | 46.4 KB | MIT | >=18 |
| @react-markdown-kit/renderer + GFM preset | 0.1.0 | 158.1 KB | 48.5 KB | MIT | >=18 |
react-markdown wins this section. It is
1.3 KB smaller gzipped on CommonMark and
2.1 KB smaller with GFM on both
sides. Size is not a reason to switch renderers.
GFM
react-markdown parses GitHub Flavored Markdown when you pass the remark-gfm plugin,
which its readme documents. The kit has the same route and a native extension.
// react-markdown
import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
<Markdown remarkPlugins={[remarkGfm]}>{content}</Markdown>
// React Markdown Kit
import Markdown, { defineMarkdownPreset, gfm } from '@react-markdown-kit/renderer'
const preset = defineMarkdownPreset({ extensions: [gfm()] })
<Markdown preset={preset}>{content}</Markdown>
Both routes are asserted to agree.
tests/gfm.test.ts runs 68 cases derived
from the GFM spec three ways: through the native gfm() extension, through
remarkPlugins={[remarkGfm]}, and through plain CommonMark. The first two must agree
after normalizing attribute order, entity spelling and whitespace between block tags
(tests/helpers/html.ts) and the third
must not produce the GFM markup, so a case that would pass without GFM cannot sit in the
corpus unless it is one of the 12 cases that assert something is not GFM, which are
named in the test so the exemption cannot spread.
tests/compatibility.test.tsx
compares remark-gfm output between the two packages directly, and the four
remarkPlugins cases match.
| Package | Gzipped | | --- | ---: | | renderer | measured | - [x] task list - [ ] ~~strikethrough~~ and https://example.com
| Package | Gzipped |
|---|---|
| renderer | measured |
- task list
-
strikethroughand https://example.com
Even. The extension saves a dependency; the plugin route is there if you prefer it.
Security defaults
The defaults are the same, and that is the measured result rather than a claim.
| Default | react-markdown 10.1.0 | React Markdown Kit |
|---|---|---|
dangerouslySetInnerHTML | Not used. The readme leads with it. | Not used. |
| Raw HTML in the source | Rendered as visible text; rehype-raw to opt in | Same, and skipHtml defaults to false in both |
javascript: in a link | Emptied by the default urlTransform | Same algorithm, same result on every probe URL |
| Plugins and components | Trusted code, not sandboxed | Trusted code, not sandboxed |
Evidence on this side:
the renderer security tests
cover raw HTML, script tags, javascript: URLs, obfuscated protocols, image sources,
relative URLs, allowedElements, and the policy applying to a precompiled document so it
cannot be bypassed. The urlTransform and skipHtml rows of
docs/COMPATIBILITY.md are the
cross-package proof: 4 of 4 and 4 of 4 identical. The policy is described in
the security model.
Both readmes say the same thing about plugins: a remark plugin, a rehype plugin or a
component you pass in is your code, and rehype-sanitize is the answer when the content
is not yours.
Even.
Streaming
A model writes Markdown a token at a time, so a chat UI hands the renderer a new prefix on every token and most prefixes are not valid Markdown.
React Markdown Kit passes.
packages/renderer/tests/streaming.test.tsx
is 59 tests that feed seven documents token by token and assert four properties on every
prefix: no prefix throws, the HTML of the closed prefix is byte-identical at every later
prefix, nothing in the closed prefix is duplicated, and the final prefix renders exactly
like the whole document rendered once. The same run is repeated through compileMarkdown
and through a mounted React root that grows token by token.
The cases are an unclosed code fence, half-written emphasis and strong, a table mid-row,
a list mid-item, a heading with no trailing newline, a link with an unclosed bracket, and
a fenced block that closes late. Half-written constructs render as the characters typed
so far: **str is the text **str, not a broken element. Two prefixes legitimately
rewrite output that already rendered, and both are pinned by tests rather than hidden: a
paragraph becomes a heading when a setext underline arrives, and a GFM autolink points at
the truncated host while the URL is still being typed. Both are CommonMark and GFM
behaving correctly.
This repository runs no streaming tests against react-markdown, and its readme
documents no streaming-specific behaviour, so nothing is claimed here about how it
behaves under partial input. If it matters to you, run the same corpus yourself.
Kit, on the evidence in this repository.
Server rendering
Both render on the server with no DOM. The kit's own suites call
renderToStaticMarkup throughout, including
the CommonMark conformance suite, and
scripts/pack-check.mjs installs the
packed tarball into a consumer with no Lexical and renders there, so the renderer entry
pulls in no browser-only code.
The difference is asynchronous work. react-markdown 10 exports MarkdownAsync and
MarkdownHooks so a plugin can await something. The kit has neither and is synchronous
only, which is listed as a difference in
docs/COMPATIBILITY.md.
The kit adds a second path: compile once, render many times.
compileMarkdown returns a MarkdownDocument that <Markdown document={...} />
renders without parsing again. On the reference machine in
benchmarks/README.md a 10 KB document
takes 16.80 ms from a string and 5.99 ms from a precompiled document, 2.8x faster,
because parsing is about two thirds of the work.
Speed on one render is the other way round. Both sides parsing GFM, same process, median
after five warm-up calls: 1 KB is 1.21x slower than react-markdown (2.69 ms against
2.23 ms), 10 KB is at parity (16.22 ms against 16.25 ms), and 100 KB is 0.89x (206.96 ms
against 233.36 ms). The 1 KB row is an open regression: it is fixed per-render cost, and
the fix, caching the processor per resolved configuration, is tracked in
benchmarks/README.md and not done.
Reproduce with pnpm bench.
react-markdown for async plugins. Kit for repeated renders of the same document.
Plugins
react-markdown takes remarkPlugins, rehypePlugins and remarkRehypeOptions. So
does the kit, and the compatibility run compares them directly: 4 of 4, 3 of 3 and 3 of 3
identical.
The kit adds extensions. An extension registers micromark syntax, mdast handlers, hast
handlers and editor behaviour together, so the same declaration serves the renderer, the
editor and a server that only compiles the document
(extensions). @react-markdown-kit/mermaid,
@react-markdown-kit/slides and @react-markdown-kit/template are built this way.
react-markdown has no equivalent, because it has no editor to share with.
One warning applies to both and is easy to miss: a plugin that changes the dialect cannot apply to a document that was already compiled without it. Compile with the extensions you render with, or pass the string (compiling a document).
Kit, if you need an editor or a diagram plugin. Even otherwise.
Migration
Two commands, both local, both shipped in the renderer package.
npx rmk-compare 'content/**/*.md' --gfm # diff your own corpus through both
npx rmk-migrate 'src/**/*.tsx' # report
npx rmk-migrate 'src/**/*.tsx' --write # apply
rmk-migrate edits import statements and nothing else, preserves your alias and quote
style, and refuses to touch MarkdownAsync, MarkdownHooks and
react-markdown/lib/*. It flags rehype-raw without a sanitizer instead of carrying
that setup across. Every refusal is a test in
tests/codemod.test.ts, and
rmk-compare exits non-zero on a difference
(tests/compare-runner.test.ts).
Three things the codemod cannot fix are listed in the alternative page and step by step in the migration guide. This is not a drop-in replacement.
FAQ
- Which is smaller, react-markdown or React Markdown Kit?
- react-markdown. One import of react-markdown 10.1.0 is smaller gzipped than one import of @react-markdown-kit/renderer 0.1.0, and the gap is about the same with GFM on both sides. Both parse with micromark; the difference is the kit document contract and policy layer. The numbers are in docs/data/bundle-sizes.json.
- Do both render raw HTML safely by default?
- Yes. Neither uses dangerouslySetInnerHTML, both render raw HTML as visible text unless you add rehype-raw, and both empty javascript: URLs with the same default urlTransform algorithm. 39 of 39 compared cases across 11 props produce identical markup.
- Can I switch from react-markdown without rewriting my props?
- The prop names and meanings match, and npx rmk-migrate rewrites the import statements only. Three things still differ: className is ignored rather than rejected, MarkdownAsync and MarkdownHooks do not exist, and a dialect plugin cannot apply to a document compiled without it. This is not a drop-in replacement.
Next
Renderer playground · @react-markdown-kit/renderer on npm · Source on GitHub
react-markdown alternative · vs markdown-to-jsx · vs Streamdown · Migration guide · Compatibility matrix · Security model