# markdown-to-jsx > CommonMark + GFM compliant markdown parser and compiler toolchain for JS/TS. Renders to React, React Native, Solid, Vue, HTML strings, or normalized markdown. Fast enough for real-time streaming. Current major version: v9. Raw HTML in the source is parsed into real elements, never `dangerouslySetInnerHTML`. Every emitted tag is overridable. GFM tables, task lists, strikethrough, autolinks, footnotes, and tag filtering are on by default. Zero runtime dependencies and no network access. Mental model: `parser(md) -> ASTNode[]` then `astTo*(ast, options) -> output`. `compiler(md, options)` is those two steps fused, and the framework `` components wrap `compiler`. Reach for `parser` only when you need to inspect or transform nodes before rendering. ## Install ```shell npm i markdown-to-jsx ``` ## Entry points Pick the entry point for your target. Each one ships only the renderer it names, so tree-shaking works. | Import | Default export | Named exports | Output | | -------------------------- | -------------- | ----------------------------------------------------------------------------- | --------------- | | `markdown-to-jsx` | `Markdown` | `compiler`, `parser`, `RuleType`, `sanitizer`, `slugify`, `MarkdownToJSX` | React elements | | `markdown-to-jsx/react` | `Markdown` | above + `astToJSX`, `MarkdownProvider`, `MarkdownContext` | React elements | | `markdown-to-jsx/native` | `Markdown` | above + `astToNative`, `MarkdownProvider`, `MarkdownContext` | RN elements | | `markdown-to-jsx/solid` | `Markdown` | above + `astToJSX`, `MarkdownProvider`, `MarkdownContext` | Solid JSX | | `markdown-to-jsx/vue` | `Markdown` | above + `astToJSX`, `MarkdownProvider`, `MarkdownOptionsKey` | Vue vnodes | | `markdown-to-jsx/html` | none | `compiler`, `parser`, `astToHTML`, `RuleType`, `sanitizer`, `slugify` | HTML string | | `markdown-to-jsx/markdown` | none | `compiler`, `parser`, `astToMarkdown`, `markdown`, `RuleType` | markdown string | The bare `markdown-to-jsx` entry is the legacy one: its React code still works but is deprecated. New React code should import `markdown-to-jsx/react`. ## Core patterns React component. Works unchanged in a Server Component and a Client Component; no `'use client'` needed. ```tsx import Markdown from 'markdown-to-jsx/react' ;{content} ``` Direct compile, no component: ```tsx import { compiler } from 'markdown-to-jsx/react' const element = compiler('# Hello world') ``` AST first, render second: ```tsx import { parser, astToJSX, RuleType } from 'markdown-to-jsx/react' const ast = parser('# Hello world') const headings = ast.filter(node => node.type === RuleType.heading) const element = astToJSX(ast) ``` HTML string for server rendering: ```ts import { compiler } from 'markdown-to-jsx/html' const html = compiler('# Hello world') // "

Hello world

" ``` Markdown in, normalized markdown out: ```ts import { compiler } from 'markdown-to-jsx/markdown' compiler('Setext\n======\n\n* a\n* b') // "# Setext\n\n- a\n- b" ``` React Native, styled per element key: ```tsx import Markdown from 'markdown-to-jsx/native' import { Linking, StyleSheet } from 'react-native' ; Linking.openURL(url), }} > {content} ``` Solid, reactive by passing an accessor: ```tsx import Markdown from 'markdown-to-jsx/solid' const [content, setContent] = createSignal('# Hello world') ;{content} ``` Vue 3, via `h()` under the hood: ```tsx import Markdown, { compiler } from 'markdown-to-jsx/vue' const vnode = compiler('# Hello world') ``` ## Options Shared across every renderer unless noted. | Option | Type | Default | Effect | | ------------------------------- | ----------------------------- | -------- | -------------------------------------------------------------------------------- | | `createElement` | `function` | - | Hook `(type, props, children)` before elements are built. JSX renderers only. | | `disableAutoLink` | `boolean` | `false` | Leave bare URLs as text. | | `disableParsingRawHTML` | `boolean` | `false` | Skip converting raw HTML to elements. | | `enforceAtxHeadings` | `boolean` | `false` | Require a space after `#` for a heading. | | `evalUnserializableExpressions` | `boolean` | `false` | Run `eval()` on JSX prop expressions. Unsafe; see Don't. | | `forceBlock` / `forceInline` | `boolean` | `false` | Pin the whole input to block or inline parsing. | | `forceWrapper` | `boolean` | `false` | Wrap even a single child. JSX renderers only. | | `ignoreHTMLBlocks` | `boolean` | `false` | Emit HTML blocks as literal text. | | `optimizeForStreaming` | `boolean` | `false` | Hold back incomplete syntax while content streams in. | | `overrides` | `object` | - | Swap the component or props used for a tag name. | | `preserveFrontmatter` | `boolean` | `false` | Render frontmatter instead of dropping it. | | `renderRule` | `function` | - | Intercept rendering per AST node, before anything else. | | `sanitizer` | `function` | built-in | Replace URL scheme sanitization. Signature `(value, tag, attribute)`. | | `slugify` | `function` | built-in | Replace heading id generation from plain text content. Duplicate ids still get `-1`, `-2`, … suffixes. | | `tagfilter` | `boolean` | `true` | Escape leading `<` on `script`, `iframe`, `style`, `title`, `textarea`, `xmp`, `noembed`, and kin; keep body and closer as inert text; allowed nested tags still render. | | `wrapper` | `string \| component \| null` | `'div'` | Element wrapping multiple children. `null` returns an array. JSX renderers only. | | `wrapperProps` | `object` | - | Props for that wrapper. | React Native adds `styles`, `onLinkPress`, and `onLinkLongPress`. ## Overrides Keys are HTML tag names, and they fire for both parsed markdown and raw HTML. A capitalized key registers a custom component usable directly in the markdown source. ```tsx import DatePicker from './date-picker' ; null, // remove entirely DatePicker, // in the markdown }, }} > {content} ``` Props the library always supplies: `a` gets `href`/`title`, `img` gets `src`/`alt`/`title`, `input[type=checkbox]` gets `checked`/`readonly`, `ol` gets `start`, `td`/`th` get `style`. Inline text renders as `span`, inline code as `code`, fenced code as `pre > code`. Common one-liners, all through the same mechanism: ```tsx /** open every link in a new tab */ { a: { props: { target: '_blank', rel: 'noopener noreferrer' } } } /** drop images but keep their alt text as visible copy */ { img: ({ alt }) => alt } /** rewrite image sources onto a CDN */ { img: props => } /** unwrap a tag, keeping its content */ { b: ({ children }) => children } /** drop a tag and its content entirely */ { iframe: () => null } ``` There is no built-in tag allow-list option. Build one by unwrapping (`({ children }) => children`) or dropping (`() => null`) each tag you want gone, or set `disableParsingRawHTML: true` to render every raw HTML tag as literal text. Override keys match the source tag's exact case: `` needs the key `MyThing`, and a `mything` key will not fire. ## renderRule Runs before every other rendering path and sees nodes that are normally skipped (`ref`, `footnote`, `frontmatter`). Call `next()` to fall through to the default. Here it swaps `:smile:` style shortcodes for emoji as text nodes are rendered: ```tsx import { RuleType } from 'markdown-to-jsx/react' const shortcodes = { smile: '🙂' } const detector = /(:[^:\s]+:)/g compiler(content, { renderRule(next, node, renderChildren, state) { if (node.type === RuleType.text && node.text.includes(':')) { return node.text .split(detector) .map(part => part.startsWith(':') && part.endsWith(':') ? shortcodes[part.slice(1, -1)] || part : part ) } return next() }, }) ``` `RuleType.text` is the hottest node type in the parser, so keep any matcher on it cheap and benchmark it. ## Code blocks and syntax highlighting A fenced block renders as `
`. The JSX renderers add a legacy `lang-js` alongside it; the HTML string renderer emits `language-js` only. Two ways to highlight, and which one you want depends on whether your highlighter takes a string or a DOM node.

Highlighters that take the code as a string (react-syntax-highlighter, Shiki, KaTeX) go through `renderRule`, where the raw text and language are both in hand:

```tsx
import { RuleType } from 'markdown-to-jsx/react'
import SyntaxHighlighter from 'react-syntax-highlighter'
import TeX from '@matejmazur/react-katex'

{String.raw`${node.text}`}
        }
        return (
          
            {node.text}
          
        )
      }
      return next()
    },
  }}
>
  {content}

```

Highlighters that mutate a mounted element (highlight.js) go through a `code` override that reads the class name:

```tsx
function HighlightedCode(props) {
  const ref = React.useRef(null)

  React.useEffect(() => {
    if (ref.current && props.className?.includes('lang-') && window.hljs) {
      window.hljs.highlightElement(ref.current)
      // hljs skips an element it has already touched unless this is cleared
      ref.current.removeAttribute('data-highlighted')
    }
  }, [props.className, props.children])

  return 
}

;{content}
```

The `code` override fires for inline backticks too, so branch on `props.className` when the two should render differently. To wrap or replace the surrounding block, override `pre`.

## Streaming

For markdown arriving token by token from an LLM or socket, `optimizeForStreaming` suppresses half-written syntax until its closing delimiter lands, so readers never see a flash of raw `**` or `[text](`.

```tsx
{content}
```

Held back: unclosed HTML tags and comments, unclosed inline code, bold, italic, strikethrough, unclosed links, and a table before its first data row. Fenced code blocks stream visibly as they arrive.

## AST

`parser()` returns a flat array of block nodes. Each node has a `type` from the `RuleType` enum plus type-specific fields.

```tsx
parser('# Hi\n\nA **bold** word.')
// [
//   { type: RuleType.heading, level: 1, id: 'hi', children: [{ type: RuleType.text, text: 'Hi' }] },
//   { type: RuleType.paragraph, children: [
//       { type: RuleType.text, text: 'A ' },
//       { type: RuleType.textFormatted, tag: 'strong', children: [...] },
//       { type: RuleType.text, text: ' word.' },
//   ]},
// ]
```

Node shapes worth knowing:

- `refCollection` `{ refs }` sits first in the array whenever the document defines link references or footnotes (footnote keys carry a `^` prefix). It is skipped during rendering; footnotes are pulled from it into a `