mrkeyoor.com_
Sat 08 Aug 21:01 UTC
npmWeb Frontendupdated 08 Aug 2026

markdown-to-jsx

markdown-to-jsx 9 is a CommonMark and GitHub Flavored Markdown parser plus a family of renderers. Separate entry points turn Markdown into React elements, React Native components, SolidJS nodes, Vue nodes, HTML strings, normalized Markdown, or a typed abstract syntax tree. The React component is the familiar face, but the package now covers much more than JSX. It parses raw HTML without dangerouslySetInnerHTML, lets you replace tags with application components, supports GFM tables and task lists, and adds URL and raw-HTML filtering. That flexibility is useful for documentation and controlled rich text, but it also creates a larger security and configuration surface than a plain Markdown renderer.

Verdict

A capable choice when custom component overrides or one parser across several UI frameworks is the point. For ordinary React Markdown with an established plugin pipeline, react-markdown is easier to compose and audit.

API stability3/5The component, compiler, overrides, wrapper, and sanitizer concepts have survived multiple releases, and version 9 keeps the old root React export for now. The v9 migration still contains real breaks: ast mode moved to parser(), entity configuration disappeared, tagfilter changed output by default, and framework-specific entry points became the preferred imports.
Docs5/5The documentation covers every renderer and option, includes v8-to-v9 migration steps, diagrams the AST node shapes, explains compiler versus parser, and gives unusually direct security warnings for raw HTML, URL schemes, SVG data URLs, and expression evaluation. It also documents rendering and JSX newline gotchas that shorter READMEs normally omit.
Maintenance5/5Version 9.10.2 was released on August 3, 2026, after several releases in June and July, and the repository was pushed again on August 8. Current work spans bug fixes, security filtering, streaming behavior, and multiple framework renderers. The repository reports 13 open issues and pull requests, a modest queue for its scope.
Ecosystem4/5The package records 4,484,691 weekly downloads and 2,386 GitHub stars, and version 9 exposes dedicated React, React Native, Solid, Vue, HTML, Markdown, and entity entry points. It does not participate in the much larger unified plugin network, so customization is powerful but mostly specific to this library's overrides, AST, and renderRule APIs.

Use it if

  • You need GFM Markdown rendered into React, React Native, Solid, Vue, or an HTML string from one parser
  • You want custom components inside Markdown through tag overrides, without injecting an HTML string into the DOM
  • You need access to a typed AST for transforms before rendering or want to normalize Markdown back to Markdown
  • You render incrementally arriving Markdown and want incomplete emphasis, links, tables, and tags suppressed until complete
Skip it if

Setup reality

Install markdown-to-jsx and import from the renderer-specific subpath you actually use. In version 9, React code should come from markdown-to-jsx/react; the root entry still exports it for compatibility but is deprecated and scheduled for removal in a future major. The package has no runtime dependencies. React, Vue, and Solid are optional peers, so install only the framework behind your chosen entry point. Both ESM and CommonJS exports and bundled declarations are present, and Node 18 or newer is required. Raw HTML parsing is on by default. Version 9 filters dangerous tag names and strips event handlers and dangerous URL attributes, while a separate sanitizer handles link schemes. Those defenses are useful, but they do not turn arbitrary custom component props into a harmless capability system: overrides decide which application components untrusted authors can instantiate. Never enable evalUnserializableExpressions for user-controlled text because it evaluates code. data:image/svg+xml remains a documented caveat when opened as a top-level navigation. JSX source text does not preserve Markdown newlines reliably, so keep substantial content in strings or .md files rather than indenting it inside JSX. Multiple block nodes get a div wrapper unless you choose another wrapper, React.Fragment, or null when using compiler. Code fences only get language classes; syntax highlighting requires your own component or library. For streaming text, enable optimizeForStreaming or users may briefly see unfinished Markdown delimiters. If moving from v8, replace compiler(source, { ast: true }) with parser(source), review the new default tagfilter, and migrate React imports to the /react entry point.

Patterns

Render Markdown in Reactrender-react

import Markdown from 'markdown-to-jsx/react';

export function Article({ source }: { source: string }) {
  return <Markdown>{source}</Markdown>;
}

Use the /react entry point in version 9. The root React export still works but is deprecated.

Render blocks inside a semantic wrapperset-wrapper

import Markdown from 'markdown-to-jsx/react';

<Markdown options={{
  wrapper: 'article',
  wrapperProps: { className: 'prose', 'data-testid': 'article' },
}}>
  {source}
</Markdown>

Multiple top-level nodes use a div by default. wrapperProps only applies when a wrapper is actually rendered.

Replace Markdown tags with React componentsoverride-elements

import Markdown from 'markdown-to-jsx/react';
import { AppLink } from './AppLink';

<Markdown options={{
  overrides: {
    a: { component: AppLink, props: { trackingSource: 'docs' } },
    h1: { props: { className: 'page-title' } },
  },
}}>
  {source}
</Markdown>

An override applies to both Markdown-generated tags and matching raw HTML tags. Audit which custom components untrusted authors can reach.

Drop an unwanted HTML elementremove-html-tag

import Markdown from 'markdown-to-jsx/react';

const Remove = () => null;

<Markdown options={{
  overrides: { iframe: Remove },
}}>
  {source}
</Markdown>

tagfilter escapes dangerous tags by default; an override returning null removes the element instead of showing its inert source.

Treat raw HTML as textdisable-raw-html

import Markdown from 'markdown-to-jsx/react';

<Markdown options={{ disableParsingRawHTML: true }}>
  {userContent}
</Markdown>

This narrows the feature surface for user content. Keep tagfilter enabled and do not enable expression evaluation.

Allow only selected URL schemescustomize-url-sanitizer

import Markdown, { sanitizer } from 'markdown-to-jsx/react';

const safeUrl = (value: string, tag: string, attribute: string) => {
  if (value.startsWith('mailto:') || value.startsWith('https://')) return value;
  return sanitizer(value, tag, attribute);
};

<Markdown options={{ sanitizer: safeUrl }}>{source}</Markdown>

The URL sanitizer and raw HTML attribute filtering are separate defenses. Returning input unchanged disables scheme protection.

Parse Markdown into an ASTparse-ast

import { parser, RuleType } from 'markdown-to-jsx/react';

const ast = parser(source);
const headings = ast.filter((node) => node.type === RuleType.heading);
console.log(headings.map((node) => ({ level: node.level, id: node.id })));

In version 9, use parser(); compiler(source, { ast: true }) was removed. The first node is often a reference collection.

Convert Markdown to an HTML stringcompile-html

import { compiler } from 'markdown-to-jsx/html';

const html = compiler('# Release notes

- Fixed login
- Added export');

This uses the dedicated HTML entry point and returns a string. Review how you later insert that string into a page.

Parse and write Markdown againnormalize-markdown

import { compiler } from 'markdown-to-jsx/markdown';

const normalized = compiler(source);

Normalization can change formatting while preserving structure, so show a diff before overwriting author-owned files.

Hide incomplete syntax during streamingrender-streaming-markdown

import Markdown from 'markdown-to-jsx/react';

export function StreamingMessage({ content }: { content: string }) {
  return (
    <Markdown options={{ optimizeForStreaming: true }}>
      {content}
    </Markdown>
  );
}

The option suppresses unfinished links, emphasis, tables, and HTML. Fenced code content remains visible while it arrives.

Generate heading IDs with a custom sluggercustomize-heading-ids

import Markdown from 'markdown-to-jsx/react';

const slugify = (text: string) => text
  .normalize('NFKD')
  .toLowerCase()
  .replace(/[^a-z0-9]+/g, '-')
  .replace(/^-|-$/g, '');

<Markdown options={{ slugify }}>{source}</Markdown>

The library still adds numeric suffixes when your slug function returns the same ID for multiple headings.

Replace fenced code blocks with a highlighterrender-code-blocks

import Markdown, { RuleType } from 'markdown-to-jsx/react';

<Markdown options={{
  renderRule(next, node, _children, state) {
    if (node.type === RuleType.codeBlock) {
      return <CodeBlock key={state.key} code={node.text} language={node.lang} />;
    }
    return next();
  },
}}>
  {source}
</Markdown>

Code fences only receive language classes by default. renderRule exposes node.lang and node.text for a real highlighter.

Alternatives

PackageRegistryPick it when
react-markdownnpmUse it for React rendering built around unified, remark, and rehype plugins
markednpmUse it when you primarily need fast Markdown-to-HTML conversion and will own sanitization
micromarknpmUse it as a lower-level CommonMark parser when a small standards-focused core matters more than components