markdown-to-jsx vs react-markdown vs remark-react
Markdown Rendering Libraries for React Comparison
1 Year
markdown-to-jsxreact-markdownremark-react
What's Markdown Rendering Libraries for React?

Markdown rendering libraries for React facilitate the conversion of Markdown text into React components, enabling developers to easily integrate rich text content into their applications. Each library offers unique features and approaches to parsing and rendering Markdown, catering to different needs and preferences in terms of flexibility, performance, and extensibility. These libraries help streamline the process of displaying formatted text, making it easier to manage content in a structured way while maintaining the ability to customize the output as needed.

Package Weekly Downloads Trend
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
markdown-to-jsx4,104,0232,101493 kB14315 days agoMIT
react-markdown3,866,90513,80651 kB0a day agoMIT
remark-react15,650525567 B05 months ago-
Feature Comparison: markdown-to-jsx vs react-markdown vs remark-react

Rendering Flexibility

  • markdown-to-jsx:

    markdown-to-jsx allows for direct conversion of Markdown to JSX, enabling developers to customize the rendered output easily. You can define how each Markdown element should be rendered, providing a high degree of control over the final appearance.

  • react-markdown:

    react-markdown offers a flexible rendering approach that supports various Markdown elements and allows for custom renderers. This means you can easily extend its functionality by providing your own components for specific Markdown elements, making it versatile for different use cases.

  • remark-react:

    remark-react integrates with the Remark ecosystem, allowing for extensive transformations of Markdown content before rendering. This enables developers to preprocess Markdown, apply plugins, and customize the rendering pipeline, offering a robust solution for complex scenarios.

Performance

  • markdown-to-jsx:

    markdown-to-jsx is lightweight and optimized for performance, making it suitable for applications where speed is crucial. Its direct conversion to JSX minimizes overhead, ensuring quick rendering of Markdown content.

  • react-markdown:

    react-markdown is designed to handle larger Markdown documents efficiently, but performance may vary depending on the complexity of the content and the number of plugins used. It is generally performant, but care should be taken when using many custom renderers or plugins.

  • remark-react:

    remark-react can introduce some overhead due to its extensive parsing capabilities and transformations. However, it is highly efficient for applications that require advanced Markdown processing, as it allows for optimizations during the rendering process.

Extensibility

  • markdown-to-jsx:

    markdown-to-jsx is straightforward and allows for basic customization but may lack advanced extensibility features compared to other libraries. It is best suited for projects that do not require extensive Markdown processing or transformations.

  • react-markdown:

    react-markdown excels in extensibility, supporting a variety of plugins and custom renderers. This makes it an excellent choice for projects that need to incorporate additional functionality, such as syntax highlighting or custom Markdown syntax.

  • remark-react:

    remark-react is highly extensible due to its integration with the Remark ecosystem, allowing developers to use plugins for various transformations and optimizations. This makes it ideal for projects that require significant customization and preprocessing of Markdown content.

Learning Curve

  • markdown-to-jsx:

    markdown-to-jsx has a gentle learning curve, making it easy for developers to get started quickly. Its straightforward API allows for rapid implementation without extensive configuration.

  • react-markdown:

    react-markdown has a moderate learning curve, especially for developers who want to leverage its full potential with custom renderers and plugins. However, its documentation is comprehensive, aiding in the learning process.

  • remark-react:

    remark-react may have a steeper learning curve due to its reliance on the Remark ecosystem and the need to understand various plugins and transformations. It is best suited for developers who are comfortable with Markdown processing and require advanced features.

Community and Support

  • markdown-to-jsx:

    markdown-to-jsx has a smaller community compared to the other libraries, which may result in limited resources and support. However, it is still actively maintained and has a straightforward implementation.

  • react-markdown:

    react-markdown has a larger community and extensive documentation, providing ample resources for developers. Its popularity ensures that issues are more likely to be addressed and that community support is readily available.

  • remark-react:

    remark-react benefits from being part of the larger Remark ecosystem, which has a strong community and a wealth of plugins. This support makes it easier for developers to find solutions and share knowledge.

How to Choose: markdown-to-jsx vs react-markdown vs remark-react
  • markdown-to-jsx:

    Choose markdown-to-jsx if you need a lightweight solution that directly converts Markdown to JSX components, allowing for easy customization of rendered elements. It is ideal for projects where you want to maintain full control over the output and styling of each Markdown element.

  • react-markdown:

    Select react-markdown if you require a more feature-rich solution that supports a wide range of Markdown syntax and offers extensibility through plugins. It is suitable for applications that need to handle complex Markdown content and require additional features like syntax highlighting or custom renderers.

  • remark-react:

    Opt for remark-react if you are already using the Remark ecosystem and want to leverage its powerful parsing capabilities. This library is ideal for projects that require extensive transformations and optimizations of Markdown content before rendering, making it a good choice for advanced use cases.

README for markdown-to-jsx

markdown-to-jsx

The most lightweight, customizable React markdown component.

npm version downloads


markdown-to-jsx uses a heavily-modified fork of simple-markdown as its parsing engine and extends it in a number of ways to make your life easier. Notably, this package offers the following additional benefits:

  • 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.

  • GFM task list support.

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

All this clocks in at around 6 kB gzipped, which is a fraction of the size of most other React markdown components.

Requires React >= 0.14.

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.

Parsing 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.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 - 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.

options.namedCodesToUnicode

By default only a couple of named html codes are converted to unicode characters:

  • & (&amp;)
  • ' (&apos;)
  • > (&gt;)
  • < (&lt;)
  • (&nbsp;)
  • " (&quot;)

Some projects require to extend this map of named codes and unicode characters. To customize this list with additional html codes pass the option namedCodesToUnicode as object with the code names needed as in the example below:

<Markdown options={{ namedCodesToUnicode: {
    le: '\u2264',
    ge: '\u2265',
    '#39': '\u0027',
} }}>This text is &le; than this text.</Markdown>;

// or

compiler('This text is &le; than this text.', namedCodesToUnicode: {
    le: '\u2264',
    ge: '\u2265',
    '#39': '\u0027',
});

// renders:

<p>This text is ≤ than this text.</p>

options.disableAutoLink

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.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>

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:

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,
        },
      }}
    />
  )
}

/**
 * Add the following tags to your page <head> to automatically load hljs and styles:

  <link
    rel="stylesheet"
    href="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/styles/nord.min.css"
  />

  * NOTE: for best performance, load individual languages you need instead of all
          of them. See their docs for more info: https://highlightjs.org/

  <script
    crossorigin
    src="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/highlight.min.js"
  ></script>
 */

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} />
}

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.

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>

Using The Compiler Directly

If desired, the compiler function is a "named" export on the markdown-to-jsx module:

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

render(compiler('# Hello world!'), document.body)

/*
    renders:

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

It accepts the following arguments:

compiler(markdown: string, options: object?)

Changelog

See Github Releases.

Donate

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

MIT