React Markdown Kit vs Streamdown for AI chat
Streamdown is Vercel's Markdown component for AI output. Its readme presents it as a
replacement for react-markdown designed for AI-powered streaming, and it powers the AI
Elements message component. @react-markdown-kit/renderer is a general React
Markdown renderer with tests that pin down its streaming behaviour. They solve the same
problem, but Streamdown ships a lot more of the finished UI.
The sections are in the same order as the other comparison pages (install size, GFM, security defaults, streaming, server rendering, plugins, migration). Facts about Streamdown come from its readme and its published package at version 2.6.0, vercel/streamdown. Facts about the kit link to the test or the generated table that produced them.
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, CSS is counted separately
and never added into the JavaScript number, and 1 KB is 1024 bytes.
| Import | Version | Minified | Gzipped | License | Peer react |
|---|---|---|---|---|---|
| @react-markdown-kit/renderer, CommonMark | 0.1.0 | 118.5 KB | 36.8 KB | MIT | >=18 |
| @react-markdown-kit/renderer + GFM preset | 0.1.0 | 158.1 KB | 48.5 KB | MIT | >=18 |
| streamdown | 2.6.0 | 506.0 KB | 152.2 KB | Apache-2.0 | ^18.0.0 || ^19.0.0 |
The kit with GFM is 103.7 KB smaller gzipped
than Streamdown for the entry that was measured,
import { Streamdown } from 'streamdown'. That's mostly a difference in scope.
Streamdown's published dependencies include marked, remend, remark-gfm,
rehype-raw, rehype-sanitize, rehype-harden, clsx and tailwind-merge, and the
component ships styled output. The kit renders unstyled elements and leaves the rest to
you.
Streamdown is also the only package compared here that isn't MIT (the measured tarball declares Apache-2.0).
On the Streamdown side there are two more costs that don't show up in the JavaScript
number, both from its installation instructions: a Tailwind @source line pointing at
node_modules/streamdown/dist/*.js, and a set of shadcn/ui CSS custom properties without
which, the readme says, components may render with missing backgrounds or borders. The
kit emits no class names unless you ask for them
(styling,
docs/STYLING.md).
GFM
Both render GitHub Flavored Markdown. Streamdown lists tables, task lists and
strikethrough as built-in features and depends on remark-gfm. On the kit it's one extra line.
import Markdown, { defineMarkdownPreset, gfm } from '@react-markdown-kit/renderer'
const preset = defineMarkdownPreset({ extensions: [gfm()] })
<Markdown preset={preset}>{content}</Markdown>
The kit's GFM is asserted by
tests/gfm.test.ts: 68 cases derived from
the GFM spec, each run through the native gfm() extension, through
remarkPlugins={[remarkGfm]}, and through plain CommonMark, where 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, unless the case is one of the 12 that assert
something is not GFM (those are named in the test so the exemption can't spread). CommonMark itself is
covered example by example in
tests/commonmark.test.ts. No
conformance suite is run here against Streamdown.
Streamdown also has things the renderer doesn't include: KaTeX math, Shiki syntax
highlighting, and Mermaid, each as an optional @streamdown/* plugin package in 2.6.0.
The kit has a Mermaid plugin of its own that draws flowcharts and sequence diagrams and
doesn't load Mermaid.js (Mermaid in React Markdown). It doesn't ship
math or syntax highlighting, so you'd add a rehype plugin for those.
Security defaults
Streamdown's readme lists "Security-first" and mentions rehype-harden, and its published
dependencies include rehype-raw, rehype-sanitize and rehype-harden. So raw HTML
goes through a sanitizer instead of being left inert. This page doesn't test that pipeline,
so all it reports is the dependency list and what the readme says.
On the kit, raw HTML never becomes markup until you opt in.
| Default | React Markdown Kit |
|---|---|
| Raw HTML in the source | Visible escaped text. skipHtml: true removes it. Neither executes it |
| Opting in | rehype-raw then rehype-sanitize, in that order, passed by you |
| URLs | http, https, irc, ircs, mailto, xmpp allowed, everything else emptied |
| A precompiled document | Same policy, so it is not a bypass |
Each row is a test in the renderer security tests, and the model is written up in the security model.
For model output, the thing to look at is what a hallucinated <script> or javascript:
link does. On the kit it ends up as text and an empty href, and you don't have to install
or configure anything for that.
Streaming
Streaming is what Streamdown was built for. Streamdown has the longer feature list here, and the kit has the published tests.
Streamdown's readme describes streaming-optimized rendering with unterminated block
parsing built on remend, and memoized rendering for efficient updates. It also exposes
animated and isAnimating props for the AI SDK's streaming status.
React Markdown Kit handles this without any option.
packages/renderer/tests/streaming.test.tsx
is 59 tests. Seven documents are fed token by token, where words stay whole and every
whitespace and punctuation character is its own token, so a fence arrives as three
separate backticks. Every prefix is asserted on four properties:
- no prefix throws,
- the HTML of the closed prefix is byte-identical at every later prefix,
- nothing from the closed prefix is duplicated,
- the last prefix renders exactly like the whole document rendered once.
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. The same run is repeated through compileMarkdown, and
a mounted React root is grown token by token to check that a precompiled document
re-renders as it grows and ends byte-identical to a root that only ever saw the finished
document. One test checks that the heading element is reused as the document grows instead
of being recreated.
Half-written constructs fall back to the characters typed so far: **str is the text
**str, [text]( is the text [text](, and a table is a paragraph until its delimiter
row is complete. An open fence never leaks its contents as markup, so a model writing
Markdown inside a fence won't flash headings. Two prefixes do rewrite output that already
rendered, and both are pinned by tests: 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.
Here is the summary you asked for.
| Metric | Value |
|---|---|
| tokens | 812 |
const partial = {
answer: 42,
The fence is still open. Its contents render as code, the finished table above it is untouched, and deleting characters from the end never throws.
Where they differ is what an incomplete construct looks like. Streamdown completes
it through remend, which closes an open **, ~~ or
` and swaps an unfinished link destination for streamdown:incomplete-link
(their features list), so half-typed syntax
renders as finished formatting that changes when the real delimiter arrives. The kit
shows the literal characters and guarantees the closed prefix above doesn't move. If you
want completion, run remend over the string before you pass it to the kit (the kit
doesn't do that for you).
Server rendering
Streamdown's published dist/index.js begins with "use client", so the component runs
on the client. Its documented usage is a React client component driven by the AI SDK's
useChat.
The kit renders on the server with no DOM. Its conformance suites call
renderToStaticMarkup, and
scripts/pack-check.mjs installs the
packed tarball into a consumer with no Lexical and renders there
(server rendering). The same component works in
a React server component, during SSR and in a static build, which is useful if the chat
transcript is also a page you want indexed.
The other server-side difference is compileMarkdown. A MarkdownDocument compiled
once renders many times 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 precompiled, 2.8x faster, because parsing is
about two thirds of the work. A finished chat message is a good fit for it.
On the other side, measured against react-markdown@10.1.0 (not Streamdown),
1 KB is 1.21x slower (2.69 ms against 2.23 ms), 10 KB is at parity (16.22 ms against
16.25 ms), 100 KB is 0.89x (206.96 ms against 233.36 ms). Short messages are the 1 KB
case, and that row is an open regression tracked in
benchmarks/README.md. Reproduce with
pnpm bench. No benchmark against Streamdown is published here.
Plugins
Streamdown takes a plugins object of @streamdown/* packages: code, mermaid,
math, cjk in the readme example. Each is installed separately and each adds its own
Tailwind @source line.
The kit takes remarkPlugins, rehypePlugins and remarkRehypeOptions, compared prop
for prop against react-markdown at 4 of 4, 3 of 3 and 3 of 3 identical
(docs/COMPATIBILITY.md), so anything
in the unified ecosystem works. It also has extensions, which register micromark syntax,
mdast handlers, hast handlers and editor behaviour in one declaration, so the renderer,
the editor and a compile-only server read the same thing
(extensions).
So structurally, Streamdown gives you a finished chat surface, while the kit gives you a document contract that the renderer, the editor (editor), the Mermaid plugin and the template plugin all share, and you assemble the surface.
Migration
There is no codemod for this direction. rmk-migrate and rmk-compare handle
react-markdown only
(compare.mjs).
-import { Streamdown } from 'streamdown'
-import 'streamdown/styles.css'
+import Markdown, { defineMarkdownPreset, gfm } from '@react-markdown-kit/renderer'
+
+const preset = defineMarkdownPreset({ extensions: [gfm()] })
-<Streamdown>{part.text}</Streamdown>
+<Markdown preset={preset}>{part.text}</Markdown>
Here's what you give up and should plan for.
- Styling. Streamdown ships styled output. The kit emits no class names by default,
so you bring a stylesheet or the opt-in
classNameshooks (styling). - Syntax highlighting and math. Shiki and KaTeX come with Streamdown's plugin packages. Here they are a rehype plugin you choose.
- Mermaid. The kit's plugin renders flowcharts and sequence diagrams and doesn't load Mermaid.js (Mermaid in React Markdown).
animatedandisAnimating. No equivalent. The kit re-renders whatever string you pass and guarantees the closed prefix does not move.
In exchange you get a smaller bundle, raw HTML that stays inert without a pipeline to configure, server rendering, a precompiled document path, and streaming behaviour covered by 59 tests you can run.
FAQ
- Which is smaller for an AI chat UI, Streamdown or React Markdown Kit?
- React Markdown Kit is smaller. One import of @react-markdown-kit/renderer 0.1.0 with the GFM preset is smaller gzipped than one import of streamdown 2.6.0, measured by scripts/compare-bundles.mjs and committed to docs/data/bundle-sizes.json. To be fair, Streamdown's number includes syntax highlighting, styling and a hardened rehype pipeline.
- Does React Markdown Kit handle partial Markdown from a model?
- Yes, and there are tests for it. packages/renderer/tests/streaming.test.tsx is 59 tests that feed seven documents token by token and assert that no prefix throws, the closed prefix stays byte-identical, nothing is duplicated, and the last prefix matches a one-shot render.
- Do I need Tailwind to render Markdown from an AI model?
- Not with React Markdown Kit. It doesn't emit class names by default and there's no stylesheet you have to load. Streamdown documents a Tailwind @source directive and a set of shadcn/ui CSS custom properties as part of its installation.
Next
Renderer playground · @react-markdown-kit/renderer on npm · Source on GitHub
react-markdown alternative · vs react-markdown · vs markdown-to-jsx · Renderer overview · Security model