JSPM

  • Created
  • Published
  • Downloads 3740720
  • Score
    100M100P100Q204566F
  • License MIT

A very fast and versatile markdown toolchain. AST, React, and HTML output available with full customization.

Package Exports

  • markdown-to-jsx
  • markdown-to-jsx/html
  • markdown-to-jsx/markdown
  • markdown-to-jsx/react

Readme

npm version downloads

markdown-to-jsx is a gfm+commonmark compliant markdown parser and compiler toolchain for JavaScript and TypeScript-based projects. It is extremely fast, capable of processing large documents fast enough for real-time interactivity.

Some special features of the library:

  • Arbitrary HTML is supported and parsed into the appropriate JSX representation without dangerouslySetInnerHTML

  • Any HTML tags rendered by the compiler and/or <Markdown> component can be overridden to include additional props or even a different HTML representation entirely.

  • All GFM special syntaxes are supported, including tables, task lists, strikethrough, autolinks, tag filtering, and more.

  • Fenced code blocks with highlight.js support; see Syntax highlighting for instructions on setting up highlight.js.

Table of Contents

Upgrading

From v8.x to v9.x

Breaking Changes:

  • ast option removed: The ast: true option on compiler() has been removed. Use the new parser() function instead to access the AST directly.
// Before (v8)
import { compiler } from 'markdown-to-jsx'
const ast = compiler('# Hello world', { ast: true })

// After (v9)
import { parser } from 'markdown-to-jsx'
const ast = parser('# Hello world')
  • namedCodesToUnicode option removed: The namedCodesToUnicode option has been removed. All named HTML entities are now supported by default via the full entity list, so custom entity mappings are no longer needed.
// Before (v8)
import { compiler } from 'markdown-to-jsx'
compiler('&le; symbol', { namedCodesToUnicode: { le: '\u2264' } })

// After (v9)
import { compiler } from 'markdown-to-jsx'
compiler('&le; symbol') // All entities supported automatically
  • tagfilter enabled by default: Dangerous HTML tags (script, iframe, style, title, textarea, xmp, noembed, noframes, plaintext) are now escaped by default in both HTML string output and React JSX output. Previously these tags were rendered as JSX elements in React output.
// Before (v8) - tags rendered as JSX elements
compiler('<script>alert("xss")</script>') // Rendered as <script> element

// After (v9) - tags escaped by default
compiler('<script>alert("xss")</script>') // Renders as <span>&lt;script&gt;</span>

// To restore old behavior:
compiler('<script>alert("xss")</script>', { tagfilter: false })

New Features:

  • New parser function: Provides direct access to the parsed AST without rendering. This is the recommended way to get AST nodes.

  • New entry points: React-specific, HTML-specific, and markdown-specific entry points are now available for better tree-shaking and separation of concerns.

// React-specific usage
import Markdown, { compiler, parser } from 'markdown-to-jsx/react'

// HTML string output
import { compiler, astToHTML, parser } from 'markdown-to-jsx/html'

// Markdown string output (round-trip compilation)
import { compiler, astToMarkdown, parser } from 'markdown-to-jsx/markdown'

Migration Guide:

  1. Replace compiler(..., { ast: true }) with parser():
// Before
import { compiler } from 'markdown-to-jsx'
const ast = compiler(markdown, { ast: true })

// After
import { parser } from 'markdown-to-jsx'
const ast = parser(markdown)
  1. Migrate React imports to /react entry point (optional but recommended):
// Before
import Markdown, { compiler } from 'markdown-to-jsx'

// After (recommended)
import Markdown, { compiler } from 'markdown-to-jsx/react'
  1. Remove namedCodesToUnicode option: All named HTML entities are now supported automatically, so you can remove any custom entity mappings.
// Before
compiler('&le; symbol', { namedCodesToUnicode: { le: '\u2264' } })

// After
compiler('&le; symbol') // Works automatically

Note: The main entry point (markdown-to-jsx) continues to work for backward compatibility, but React code there is deprecated and will be removed in a future major release. Consider migrating to markdown-to-jsx/react for React-specific usage.

### Older Migration Guides

From v7.x to v8.x

Breaking Changes:

  • Type ParserResult renamed to ASTNode - If you were using MarkdownToJSX.ParserResult in your code, update to MarkdownToJSX.ASTNode
// Before
const nodes: MarkdownToJSX.ParserResult[] = parse(markdown)

// After
const nodes: MarkdownToJSX.ASTNode[] = parse(markdown)
  • Multiple RuleType enums consolidated into RuleType.textFormatted - If you were checking for RuleType.textBolded, RuleType.textEmphasized, RuleType.textMarked, or RuleType.textStrikethroughed, update to check for RuleType.textFormatted and inspect the node's boolean flags:
// Before
if (node.type === RuleType.textBolded) { ... }

// After
if (node.type === RuleType.textFormatted && node.bold) { ... }

Installation

Install markdown-to-jsx with your favorite package manager.

npm i markdown-to-jsx

Usage

markdown-to-jsx exports a React component by default for easy JSX composition:

ES6-style usage*:

import Markdown from 'markdown-to-jsx'
import React from 'react'
import { render } from 'react-dom'

render(<Markdown># Hello world!</Markdown>, document.body)

/*
    renders:

    <h1>Hello world!</h1>
 */

* NOTE: JSX does not natively preserve newlines in multiline text. In general, writing markdown directly in JSX is discouraged and it's a better idea to keep your content in separate .md files and require them, perhaps using webpack's raw-loader.

Entry Points

markdown-to-jsx provides multiple entry points for different use cases:

Main

The legacy*default entry point exports everything, including the React compiler and component:

import Markdown, { compiler, parser } from 'markdown-to-jsx'

The React code in this entry point is deprecated and will be removed in a future major release, migrate to markdown-to-jsx/react.

React

For React-specific usage, import from the /react entry point:

import Markdown, { compiler, parser, astToJSX } from 'markdown-to-jsx/react'

// Use compiler for markdown → JSX
const jsxElement = compiler('# Hello world')

const markdown = `# Hello world`

function App() {
  return <Markdown children={markdown} />
}

// Or use parser + astToJSX for total control
const ast = parser('# Hello world')
const jsxElement2 = astToJSX(ast)

HTML

For HTML string output (server-side rendering), import from the /html entry point:

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

// Convenience function that combines parsing and HTML rendering
const htmlString = compiler('# Hello world')
// Returns: '<h1>Hello world</h1>'

// Or use parser + html separately for more control
const ast = parser('# Hello world')
const htmlString2 = html(ast)

Markdown

For markdown-to-markdown compilation (normalization and formatting), import from the /markdown entry point:

import { compiler, astToMarkdown, parser } from 'markdown-to-jsx/markdown'

// Convenience function that parses and recompiles markdown
const normalizedMarkdown = compiler('# Hello  world\n\nExtra spaces!')
// Returns: '# Hello world\n\nExtra spaces!\n'

// Or work with AST directly
const ast = parser('# Hello  world')
const normalizedMarkdown2 = astToMarkdown(ast)
// Returns: '# Hello world\n'

Library Options

options.forceBlock

By default, the compiler will try to make an intelligent guess about the content passed and wrap it in a <div>, <p>, or <span> as needed to satisfy the "inline"-ness of the markdown. For instance, this string would be considered "inline":

Hello. _Beautiful_ day isn't it?

But this string would be considered "block" due to the existence of a header tag, which is a block-level HTML element:

# Whaddup?

However, if you really want all input strings to be treated as "block" layout, simply pass options.forceBlock = true like this:

<Markdown options={{ forceBlock: true }}>Hello there old chap!</Markdown>

// or

compiler('Hello there old chap!', { forceBlock: true })

// renders
<p>Hello there old chap!</p>

options.forceInline

The inverse is also available by passing options.forceInline = true:

<Markdown options={{ forceInline: true }}># You got it babe!</Markdown>

// or
compiler('# You got it babe!', { forceInline: true })

// renders
<span># You got it babe!</span>

options.wrapper

When there are multiple children to be rendered, the compiler will wrap the output in a div by default. You can override this default by setting the wrapper option to either a string (React Element) or a component.

const str = '# Heck Yes\n\nThis is great!'

<Markdown options={{ wrapper: 'article' }}>
  {str}
</Markdown>;

// or

compiler(str, { wrapper: 'article' });

// renders

<article>
  <h1>Heck Yes</h1>
  <p>This is great!</p>
</article>
Other useful recipes

To get an array of children back without a wrapper, set wrapper to null. This is particularly useful when using compiler(…) directly.

compiler('One\n\nTwo\n\nThree', { wrapper: null })

// returns
;[<p>One</p>, <p>Two</p>, <p>Three</p>]

To render children at the same DOM level as <Markdown> with no HTML wrapper, set wrapper to React.Fragment. This will still wrap your children in a React node for the purposes of rendering, but the wrapper element won't show up in the DOM.

options.wrapperProps

Props to apply to the wrapper element when wrapper is used.

<Markdown options={{
  wrapper: 'article',
  wrapperProps: { className: 'post', 'data-testid': 'markdown-content' }
}}>
  # Hello World
</Markdown>

// renders
<article class="post" data-testid="markdown-content">
  <h1>Hello World</h1>
</article>

options.forceWrapper

By default, the compiler does not wrap the rendered contents if there is only a single child. You can change this by setting forceWrapper to true. If the child is inline, it will not necessarily be wrapped in a span.

// Using `forceWrapper` with a single, inline child…
<Markdown options={{ wrapper: 'aside', forceWrapper: true }}>
  Mumble, mumble…
</Markdown>

// renders

<aside>Mumble, mumble…</aside>

options.overrides - Void particular banned tags

Pass the options.overrides prop to the compiler or <Markdown> component with an implementation that return null for tags you wish to exclude from the rendered output. This provides complete removal of tags from the output.

Note: The tagfilter option provides default escaping of dangerous tags (script, iframe, style, title, textarea, xmp, noembed, noframes, plaintext). Use overrides when you need to:

  • Remove additional tags not covered by tagfilter (like object)
  • Have more control over tag removal vs. escaping
  • Disable tagfilter but still want to remove specific tags

For example, to void the iframe tag:

import Markdown from 'markdown-to-jsx'
import React from 'react'
import { render } from 'react-dom'

render(
  <Markdown options={{ overrides: { iframe: () => null } }}>
    <iframe src="https://potentially-malicious-web-page.com/"></iframe>
  </Markdown>,
  document.body
)

// renders: ""

The library does not void any tags by default (except through tagfilter escaping), allowing you to choose the appropriate security approach for your use case.

options.overrides - Override Any HTML Tag's Representation

Pass the options.overrides prop to the compiler or <Markdown> component to seamlessly revise the rendered representation of any HTML tag. You can choose to change the component itself, add/change props, or both.

import Markdown from 'markdown-to-jsx'
import React from 'react'
import { render } from 'react-dom'

// surprise, it's a div instead!
const MyParagraph = ({ children, ...props }) => <div {...props}>{children}</div>

render(
  <Markdown
    options={{
      overrides: {
        h1: {
          component: MyParagraph,
          props: {
            className: 'foo',
          },
        },
      },
    }}
  >
    # Hello world!
  </Markdown>,
  document.body
)

/*
    renders:

    <div class="foo">
        Hello World
    </div>
 */

If you only wish to provide a component override, a simplified syntax is available:

{
    overrides: {
        h1: MyParagraph,
    },
}

Depending on the type of element, there are some props that must be preserved to ensure the markdown is converted as intended. They are:

  • a: title, href
  • img: title, alt, src
  • input[type="checkbox"]: checked, readonly (specifically, the one rendered by a GFM task list)
  • ol: start
  • td: style
  • th: style

Any conflicts between passed props and the specific properties above will be resolved in favor of markdown-to-jsx's code.

Some element mappings are a bit different from other libraries, in particular:

  • span: Used for inline text.
  • code: Used for inline code.
  • pre > code: Code blocks are a code element with a pre as its direct ancestor.

options.overrides - Rendering Arbitrary React Components

One of the most interesting use cases enabled by the HTML syntax processing in markdown-to-jsx is the ability to use any kind of element, even ones that aren't real HTML tags like React component classes.

By adding an override for the components you plan to use in markdown documents, it's possible to dynamically render almost anything. One possible scenario could be writing documentation:

import Markdown from 'markdown-to-jsx'
import React from 'react'
import { render } from 'react-dom'

import DatePicker from './date-picker'

const md = `
# DatePicker

The DatePicker works by supplying a date to bias towards,
as well as a default timezone.

<DatePicker biasTowardDateTime="2017-12-05T07:39:36.091Z" timezone="UTC+5" />
`

render(
  <Markdown
    children={md}
    options={{
      overrides: {
        DatePicker: {
          component: DatePicker,
        },
      },
    }}
  />,
  document.body
)

markdown-to-jsx also handles JSX interpolation syntax, but in a minimal way to not introduce a potential attack vector. Interpolations are sent to the component as their raw string, which the consumer can then eval() or process as desired to their security needs.

In the following case, DatePicker could simply run parseInt() on the passed startTime for example:

import Markdown from 'markdown-to-jsx'
import React from 'react'
import { render } from 'react-dom'

import DatePicker from './date-picker'

const md = `
# DatePicker

The DatePicker works by supplying a date to bias towards,
as well as a default timezone.

<DatePicker
  biasTowardDateTime="2017-12-05T07:39:36.091Z"
  timezone="UTC+5"
  startTime={1514579720511}
/>
`

render(
  <Markdown
    children={md}
    options={{
      overrides: {
        DatePicker: {
          component: DatePicker,
        },
      },
    }}
  />,
  document.body
)

Another possibility is to use something like recompose's withProps() HOC to create various pregenerated scenarios and then reference them by name in the markdown:

import Markdown from 'markdown-to-jsx'
import React from 'react'
import { render } from 'react-dom'
import withProps from 'recompose/withProps'

import DatePicker from './date-picker'

const DecemberDatePicker = withProps({
  range: {
    start: new Date('2017-12-01'),
    end: new Date('2017-12-31'),
  },
  timezone: 'UTC+5',
})(DatePicker)

const md = `
# DatePicker

The DatePicker works by supplying a date to bias towards,
as well as a default timezone.

<DatePicker
  biasTowardDateTime="2017-12-05T07:39:36.091Z"
  timezone="UTC+5"
  startTime={1514579720511}
/>

Here's an example of a DatePicker pre-set to only the month of December:

<DecemberDatePicker />
`

render(
  <Markdown
    children={md}
    options={{
      overrides: {
        DatePicker,
        DecemberDatePicker,
      },
    }}
  />,
  document.body
)

options.createElement - Custom React.createElement behavior

Sometimes, you might want to override the React.createElement default behavior to hook into the rendering process before the JSX gets rendered. This might be useful to add extra children or modify some props based on runtime conditions. The function mirrors the React.createElement function, so the params are type, [props], [...children]:

import Markdown from 'markdown-to-jsx'
import React from 'react'
import { render } from 'react-dom'

const md = `
# Hello world
`

render(
  <Markdown
    children={md}
    options={{
      createElement(type, props, children) {
        return (
          <div className="parent">
            {React.createElement(type, props, children)}
          </div>
        )
      },
    }}
  />,
  document.body
)

options.enforceAtxHeadings

Forces the compiler to have space between hash sign # and the header text which is explicitly stated in the most of the markdown specs.

The opening sequence of # characters must be followed by a space or by the end of line.

options.renderRule

Supply your own rendering function that can selectively override how rules are rendered (note, this is different than options.overrides which operates at the HTML tag level and is more general). You can use this functionality to do pretty much anything with an established AST node; here's an example of selectively overriding the "codeBlock" rule to process LaTeX syntax using the @matejmazur/react-katex library:

import Markdown, { RuleType } from 'markdown-to-jsx'
import TeX from '@matejmazur/react-katex'

const exampleContent =
  'Some important formula:\n\n```latex\nmathbb{N} = { a in mathbb{Z} : a > 0 }\n```\n'

function App() {
  return (
    <Markdown
      children={exampleContent}
      options={{
        renderRule(next, node, renderChildren, state) {
          if (node.type === RuleType.codeBlock && node.lang === 'latex') {
            return (
              <TeX as="div" key={state.key}>{String.raw`${node.text}`}</TeX>
            )
          }

          return next()
        },
      }}
    />
  )
}

options.sanitizer

By default a lightweight URL sanitizer function is provided to avoid common attack vectors that might be placed into the href of an anchor tag, for example. The sanitizer receives the input, the HTML tag being targeted, and the attribute name. The original function is available as a library export called sanitizer.

This can be overridden and replaced with a custom sanitizer if desired via options.sanitizer:

// sanitizer in this situation would receive:
// ('javascript:alert("foo")', 'a', 'href')

;<Markdown options={{ sanitizer: (value, tag, attribute) => value }}>
  {`[foo](javascript:alert("foo"))`}
</Markdown>

// or

compiler('[foo](javascript:alert("foo"))', {
  sanitizer: (value, tag, attribute) => value,
})

options.slugify

By default, a lightweight deburring function is used to generate an HTML id from headings. You can override this by passing a function to options.slugify. This is helpful when you are using non-alphanumeric characters (e.g. Chinese or Japanese characters) in headings. For example:

<Markdown options={{ slugify: str => str }}># 中文</Markdown>

// or

compiler('# 中文', { slugify: str => str })

// renders:
<h1 id="中文">中文</h1>

The original function is available as a library export called slugify.

By default, bare URLs in the markdown document will be converted into an anchor tag. This behavior can be disabled if desired.

<Markdown options={{ disableAutoLink: true }}>
  The URL https://quantizor.dev will not be rendered as an anchor tag.
</Markdown>

// or

compiler(
  'The URL https://quantizor.dev will not be rendered as an anchor tag.',
  { disableAutoLink: true }
)

// renders:

<span>
  The URL https://quantizor.dev will not be rendered as an anchor tag.
</span>

options.preserveFrontmatter

By default, YAML frontmatter at the beginning of markdown documents is parsed but not rendered in the output. Set this option to true to include the frontmatter in the rendered output. For HTML/JSX output, frontmatter is rendered as a <pre> element. For markdown-to-markdown compilation, frontmatter is included in the output markdown.

Compiler Type Default Behavior When preserveFrontmatter: true When preserveFrontmatter: false
React/HTML ❌ Don't render frontmatter ✅ Render as <pre> element ❌ Don't render frontmatter
Markdown-to-Markdown ✅ Preserve frontmatter ✅ Preserve frontmatter ❌ Exclude frontmatter
<Markdown options={{ preserveFrontmatter: true }}>
{`---
title: My Document
author: John Doe
---

# Content

This is the main content.`
}
</Markdown>

// renders:

<div>
  <pre>---
title: My Document
author: John Doe
---</pre>
  <h1>Content</h1>
  <p>This is the main content.</p>
</div>

For markdown-to-markdown compilation:

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

const markdown = `---
title: My Document
author: John Doe
---

# Content`

// With preserveFrontmatter: true (default)
compiler(markdown, { preserveFrontmatter: true })
// returns: "---\ntitle: My Document\nauthor: John Doe\n---\n\n# Content"

// With preserveFrontmatter: false
compiler(markdown, { preserveFrontmatter: false })
// returns: "# Content"

options.disableParsingRawHTML

By default, raw HTML is parsed to JSX. This behavior can be disabled if desired.

<Markdown options={{ disableParsingRawHTML: true }}>
    This text has <span>html</span> in it but it won't be rendered
</Markdown>;

// or

compiler('This text has <span>html</span> in it but it won't be rendered', { disableParsingRawHTML: true });

// renders:

<span>This text has &lt;span&gt;html&lt;/span&gt; in it but it won't be rendered</span>

options.tagfilter

By default, dangerous HTML tags are filtered and escaped to prevent XSS attacks. This applies to both HTML string output and React JSX output. The following tags are filtered: script, iframe, style, title, textarea, xmp, noembed, noframes, plaintext.

// Tags are escaped by default (GFM-compliant)
compiler('<script>alert("xss")</script>')
// HTML output: '<span>&lt;script&gt;</span>'
// React output: <span>&lt;script&gt;</span>

// Disable tag filtering:
compiler('<script>alert("xss")</script>', { tagfilter: false })
// HTML output: '<script></script>'
// React output: <script></script>

Note: Even when tagfilter is disabled, other security measures remain active:

  • URL sanitization preventing javascript: and vbscript: schemes in href and src attributes
  • Protection against data: URLs (except safe data:image/* MIME types)

Syntax highlighting

When using fenced code blocks with language annotation, that language will be added to the <code> element as class="lang-${language}". For best results, you can use options.overrides to provide an appropriate syntax highlighting integration like this one using highlight.js:

<!-- Add the following tags to your page <head> to automatically load hljs and styles: -->
<link
  rel="stylesheet"
  href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/obsidian.min.css"
/>

<script
  crossorigin
  src="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/highlight.min.js"
></script>
import { Markdown, RuleType } from 'markdown-to-jsx'

const mdContainingFencedCodeBlock = '```js\nconsole.log("Hello world!");\n```\n'

function App() {
  return (
    <Markdown
      children={mdContainingFencedCodeBlock}
      options={{
        overrides: {
          code: SyntaxHighlightedCode,
        },
      }}
    />
  )
}

function SyntaxHighlightedCode(props) {
  const ref = (React.useRef < HTMLElement) | (null > null)

  React.useEffect(() => {
    if (ref.current && props.className?.includes('lang-') && window.hljs) {
      window.hljs.highlightElement(ref.current)

      // hljs won't reprocess the element unless this attribute is removed
      ref.current.removeAttribute('data-highlighted')
    }
  }, [props.className, props.children])

  return <code {...props} ref={ref} />
}

Handling shortcodes

For Slack-style messaging with arbitrary shortcodes like 😄, you can use options.renderRule to hook into the plain text rendering and adjust things to your liking, for example:

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

const shortcodeMap = {
  smile: '🙂',
}

const detector = /(:[^:]+:)/g

const replaceEmoji = (text: string): React.ReactNode => {
  return text.split(detector).map((part, index) => {
    if (part.startsWith(':') && part.endsWith(':')) {
      const shortcode = part.slice(1, -1)

      return <span key={index}>{shortcodeMap[shortcode] || part}</span>
    }

    return part
  })
}

function Example() {
  return (
    <Markdown
      options={{
        renderRule(next, node) {
          if (node.type === RuleType.text && detector.test(node.text)) {
            return replaceEmoji(node.text)
          }

          return next()
        },
      }}
    >
      {`On a beautiful summer day, all I want to do is 😄.`}
    </Markdown>
  )
}

// renders
// <span>On a beautiful summer day, all I want to do is <span>🙂</span>.</span>

When you use options.renderRule, any React-renderable JSX may be returned including images and GIFs. Ensure you benchmark your solution as the text rule is one of the hottest paths in the system!

Getting the smallest possible bundle size

Many development conveniences are placed behind process.env.NODE_ENV !== "production" conditionals. When bundling your app, it's a good idea to replace these code snippets such that a minifier (like uglify) can sweep them away and leave a smaller overall bundle.

Here are instructions for some of the popular bundlers:

Usage with Preact

Everything will work just fine! Simply Alias react to preact/compat like you probably already are doing.

AST Anatomy

The Abstract Syntax Tree (AST) is a structured representation of parsed markdown. Each node in the AST has a type property that identifies its kind, and type-specific properties.

Important: The first node in the AST is typically a RuleType.refCollection node that contains all reference definitions found in the document, including footnotes (stored with keys prefixed with ^). This node is skipped during rendering but is useful for accessing reference data. Footnotes are automatically extracted from the refCollection and rendered in a <footer> element by both compiler() and astToJSX().

Node Types

The AST consists of the following node types (use RuleType to check node types):

Block-level nodes:

  • RuleType.heading - Headings (# Heading)
    { type: RuleType.heading, level: 1, id: "heading", children: [...] }
  • RuleType.paragraph - Paragraphs
    { type: RuleType.paragraph, children: [...] }
  • RuleType.codeBlock - Fenced code blocks (```)
    { type: RuleType.codeBlock, lang: "javascript", text: "code content" }
  • RuleType.blockQuote - Blockquotes (>)
    { type: RuleType.blockQuote, children: [...], alert?: "note" }
  • RuleType.orderedList / RuleType.unorderedList - Lists
    { type: RuleType.orderedList, items: [[...]], start?: 1 }
    { type: RuleType.unorderedList, items: [[...]] }
  • RuleType.table - Tables
    { type: RuleType.table, header: [...], cells: [[...]], align: [...] }
  • RuleType.htmlBlock - HTML blocks
    { type: RuleType.htmlBlock, tag: "div", attrs: {}, children: [...] }

Inline nodes:

  • RuleType.text - Plain text
    { type: RuleType.text, text: "Hello world" }
  • RuleType.textFormatted - Bold, italic, etc.
    { type: RuleType.textFormatted, tag: "strong", children: [...] }
  • RuleType.codeInline - Inline code (`)
    { type: RuleType.codeInline, text: "code" }
  • RuleType.link - Links
    { type: RuleType.link, target: "https://example.com", children: [...] }
  • RuleType.image - Images
    { type: RuleType.image, target: "image.png", alt: "description" }

Other nodes:

  • RuleType.breakLine - Hard line breaks ( )
  • RuleType.breakThematic - Horizontal rules (---)
  • RuleType.gfmTask - GFM task list items (- [ ])
  • RuleType.ref - Reference definition node (not rendered, stored in refCollection)
  • RuleType.refCollection - Reference definitions collection (appears at AST root, includes footnotes with ^ prefix)
  • RuleType.footnote - Footnote definition node (not rendered, stored in refCollection)
  • RuleType.footnoteReference - Footnote reference ([^identifier])
  • RuleType.frontmatter - YAML frontmatter blocks
    { type: RuleType.frontmatter, text: "---\ntitle: My Title\n---" }
  • RuleType.htmlComment - HTML comment nodes
    { type: RuleType.htmlComment, text: "<!-- comment -->" }
  • RuleType.htmlSelfClosing - Self-closing HTML tags
    { type: RuleType.htmlSelfClosing, tag: "img", attrs: { src: "image.png" } }

Example AST Structure

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

const ast = parser(`# Hello World

This is a **paragraph** with [a link](https://example.com).

[linkref]: https://example.com

```javascript
console.log('code')
```

`)

// AST structure:
[
  // Reference collection (first node, if references exist)
  {
    type: RuleType.refCollection,
    refs: {
      linkref: { target: 'https://example.com', title: undefined },
    },
  },
  {
    type: RuleType.heading,
    level: 1,
    id: 'hello-world',
    children: [{ type: RuleType.text, text: 'Hello World' }],
  },
  {
    type: RuleType.paragraph,
    children: [
      { type: RuleType.text, text: 'This is a ' },
      {
        type: RuleType.textFormatted,
        tag: 'strong',
        children: [{ type: RuleType.text, text: 'paragraph' }],
      },
      { type: RuleType.text, text: ' with ' },
      {
        type: RuleType.link,
        target: 'https://example.com',
        children: [{ type: RuleType.text, text: 'a link' }],
      },
      { type: RuleType.text, text: '.' },
    ],
  },
  {
    type: RuleType.codeBlock,
    lang: 'javascript',
    text: "console.log('code')",
  },
]

Type Checking

Use the RuleType enum to identify AST nodes:

import { RuleType } from 'markdown-to-jsx'

if (node.type === RuleType.heading) {
  const heading = node as MarkdownToJSX.HeadingNode
  console.log(`Heading level ${heading.level}: ${heading.id}`)
}

When to use compiler vs parser vs <Markdown>:

  • Use <Markdown> when you need a simple React component that renders markdown to JSX.
  • Use compiler when you need React JSX output from markdown (the component uses this internally).
  • Use parser + astToJSX when you need the AST for custom processing before rendering to JSX, or just the AST itself.

Gotchas

Passing props to stringified React components

Using the options.overrides functionality to render React components, props are passed into the component in stringifed form. It is up to you to parse the string to make use of the data.

const Table: React.FC<
  JSX.IntrinsicElements['table'] & {
    columns: string
    dataSource: string
  }
> = ({ columns, dataSource, ...props }) => {
  const parsedColumns = JSON.parse(columns)
  const parsedData = JSON.parse(dataSource)

  return (
    <div {...props}>
      <h1>Columns</h1>
      {parsedColumns.map(column => (
        <span key={column.key}>{column.title}</span>
      ))}

      <h2>Data</h2>
      {parsedData.map(datum => (
        <span key={datum.key}>{datum.Month}</span>
      ))}
    </div>
  )
}

/**
 * Example HTML in markdown:
 *
 * <Table
 *    columns={[{ title: 'Month', dataIndex: 'Month', key: 'Month' }]}
 *    dataSource={[
 *      {
 *        Month: '2024-09-01',
 *        'Forecasted Revenue': '$3,137,678.85',
 *        'Forecasted Expenses': '$2,036,660.28',
 *        key: 0,
 *      },
 *    ]}
 *  />
 */

Significant indentation inside arbitrary HTML

People usually write HTML like this:

<div>Hey, how are you?</div>

Note the leading spaces before the inner content. This sort of thing unfortunately clashes with existing markdown syntaxes since 4 spaces === a code block and other similar collisions.

To get around this, markdown-to-jsx left-trims approximately as much whitespace as the first line inside the HTML block. So for example:

<div># Hello How are you?</div>

The two leading spaces in front of "# Hello" would be left-trimmed from all lines inside the HTML block. In the event that there are varying amounts of indentation, only the amount of the first line is trimmed.

NOTE! These syntaxes work just fine when you aren't writing arbitrary HTML wrappers inside your markdown. This is very much an edge case of an edge case. 🙃

Code blocks

⛔️

<div>
    var some = code();
</div>

<div>
```js
var some = code();
```
</div>

Changelog

See Github Releases.

Like this library? It's developed entirely on a volunteer basis; chip in a few bucks if you can via the Sponsor link!