UNPKG

19.6 kBMarkdownView Raw
1# react-markdown
2
3[![Build][build-badge]][build]
4[![Coverage][coverage-badge]][coverage]
5[![Downloads][downloads-badge]][downloads]
6[![Size][size-badge]][size]
7[![Sponsors][sponsors-badge]][collective]
8[![Backers][backers-badge]][collective]
9[![Chat][chat-badge]][chat]
10
11Markdown component for React using [**remark**][remark].
12
13[Learn markdown here][learn] and [check out the demo here][demo].
14
15## Install
16
17This package is [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c):
18Node 12+ is needed to use it and it must be `import`ed instead of `require`d.
19
20[npm][]:
21
22```sh
23npm install react-markdown
24```
25
26## Why this one?
27
28There are other ways for markdown in React out there so why use this one?
29The two main reasons are that they often rely on `dangerouslySetInnerHTML` or
30have bugs with how they handle markdown.
31`react-markdown` uses a syntax tree to build the virtual dom which allows for
32updating only the changing DOM instead of completely overwriting.
33`react-markdown` is 100% CommonMark (optionally GFM) compliant and has
34extensions to support custom syntax.
35
36## Use
37
38A basic hello world:
39
40```jsx
41import React from 'react'
42import ReactMarkdown from 'react-markdown'
43import ReactDom from 'react-dom'
44
45ReactDom.render(<ReactMarkdown># Hello, *world*!</ReactMarkdown>, document.body)
46```
47
48<details>
49<summary>Show equivalent JSX</summary>
50
51```jsx
52<h1>
53 Hello, <em>world</em>!
54</h1>
55```
56
57</details>
58
59Here is an example that shows passing the markdown as a string and how
60to use a plugin ([`remark-gfm`][gfm], which adds support for strikethrough,
61tables, tasklists and URLs directly):
62
63```jsx
64import React from 'react'
65import ReactDom from 'react-dom'
66import ReactMarkdown from 'react-markdown'
67import remarkGfm from 'remark-gfm'
68
69const markdown = `Just a link: https://reactjs.com.`
70
71ReactDom.render(
72 <ReactMarkdown children={markdown} remarkPlugins={[remarkGfm]} />,
73 document.body
74)
75```
76
77<details>
78<summary>Show equivalent JSX</summary>
79
80```jsx
81<p>
82 Just a link: <a href="https://reactjs.com">https://reactjs.com</a>.
83</p>
84```
85
86</details>
87
88## API
89
90This package exports the following identifier: `uriTransformer`.
91The default export is `ReactMarkdown`.
92
93### `props`
94
95* `children` (`string`, default: `''`)\
96 Markdown to parse
97* `className` (`string?`)\
98 Wrap the markdown in a `div` with this class name
99* `skipHtml` (`boolean`, default: `false`)\
100 Ignore HTML in Markdown completely
101* `sourcePos` (`boolean`, default: `false`)\
102 Pass a prop to all components with a serialized position
103 (`data-sourcepos="3:1-3:13"`)
104* `rawSourcePos` (`boolean`, default: `false`)\
105 Pass a prop to all components with their [position][]
106 (`sourcePosition: {start: {line: 3, column: 1}, end:…}`)
107* `includeElementIndex` (`boolean`, default: `false`)\
108 Pass the `index` (number of elements before it) and `siblingCount` (number
109 of elements in parent) as props to all components
110* `allowedElements` (`Array.<string>`, default: `undefined`)\
111 Tag names to allow (can’t combine w/ `disallowedElements`).
112 By default all elements are allowed
113* `disallowedElements` (`Array.<string>`, default: `undefined`)\
114 Tag names to disallow (can’t combine w/ `allowedElements`).
115 By default no elements are disallowed
116* `allowElement` (`(element, index, parent) => boolean?`, optional)\
117 Function called to check if an element is allowed (when truthy) or not.
118 `allowedElements` / `disallowedElements` is used first!
119* `unwrapDisallowed` (`boolean`, default: `false`)\
120 Extract (unwrap) the children of not allowed elements.
121 By default, when `strong` is not allowed, it and it’s children is dropped,
122 but with `unwrapDisallowed` the element itself is dropped but the children
123 used
124* `linkTarget` (`string` or `(href, children, title) => string`, optional)\
125 Target to use on links (such as `_blank` for `<a target="_blank"…`)
126* `transformLinkUri` (`(href, children, title) => string`, default:
127 [`./uri-transformer.js`][uri], optional)\
128 URL to use for links.
129 The default allows only `http`, `https`, `mailto`, and `tel`, and is
130 exported from this module as `uriTransformer`.
131 Pass `null` to allow all URLs.
132 See [security][]
133* `transformImageUri` (`(src, alt, title) => string`, default:
134 [`./uri-transformer.js`][uri], optional)\
135 Same as `transformLinkUri` but for images
136* `components` (`Object.<string, Component>`, default: `{}`)\
137 Object mapping tag names to React components
138* `remarkPlugins` (`Array.<Plugin>`, default: `[]`)\
139 List of [remark plugins][remark-plugins] to use.
140 See the next section for examples on how to pass options
141* `rehypePlugins` (`Array.<Plugin>`, default: `[]`)\
142 List of [rehype plugins][rehype-plugins] to use.
143 See the next section for examples on how to pass options
144
145## Examples
146
147### Use a plugin
148
149This example shows how to use a remark plugin.
150In this case, [`remark-gfm`][gfm], which adds support for
151strikethrough, tables, tasklists and URLs directly:
152
153```jsx
154import React from 'react'
155import ReactMarkdown from 'react-markdown'
156import ReactDom from 'react-dom'
157import remarkGfm from 'remark-gfm'
158
159const markdown = `A paragraph with *emphasis* and **strong importance**.
160
161> A block quote with ~strikethrough~ and a URL: https://reactjs.org.
162
163* Lists
164* [ ] todo
165* [x] done
166
167A table:
168
169| a | b |
170| - | - |
171`
172
173ReactDom.render(
174 <ReactMarkdown children={markdown} remarkPlugins={[remarkGfm]} />,
175 document.body
176)
177```
178
179<details>
180<summary>Show equivalent JSX</summary>
181
182```jsx
183<>
184 <p>
185 A paragraph with <em>emphasis</em> and <strong>strong importance</strong>.
186 </p>
187 <blockquote>
188 <p>
189 A block quote with <del>strikethrough</del> and a URL:{' '}
190 <a href="https://reactjs.org">https://reactjs.org</a>.
191 </p>
192 </blockquote>
193 <ul>
194 <li>Lists</li>
195 <li>
196 <input checked={false} readOnly={true} type="checkbox" /> todo
197 </li>
198 <li>
199 <input checked={true} readOnly={true} type="checkbox" /> done
200 </li>
201 </ul>
202 <p>A table:</p>
203 <table>
204 <thead>
205 <tr>
206 <td>a</td>
207 <td>b</td>
208 </tr>
209 </thead>
210 </table>
211</>
212```
213
214</details>
215
216### Use a plugin with options
217
218This example shows how to use a plugin and give it options.
219To do that, use an array with the plugin at the first place, and the options
220second.
221[`remark-gfm`][gfm] has an option to allow only double tildes for strikethrough:
222
223```jsx
224import React from 'react'
225import ReactMarkdown from 'react-markdown'
226import ReactDom from 'react-dom'
227import remarkGfm from 'remark-gfm'
228
229ReactDom.render(
230 <ReactMarkdown remarkPlugins={[[remarkGfm, {singleTilde: false}]]}>
231 This ~is not~ strikethrough, but ~~this is~~!
232 </ReactMarkdown>,
233 document.body
234)
235```
236
237<details>
238<summary>Show equivalent JSX</summary>
239
240```jsx
241<p>
242 This ~is not~ strikethrough, but <del>this is</del>!
243</p>
244```
245
246</details>
247
248### Use custom components (syntax highlight)
249
250This example shows how you can overwrite the normal handling of an element by
251passing a component.
252In this case, we apply syntax highlighting with the seriously super amazing
253[`react-syntax-highlighter`][react-syntax-highlighter] by
254[**@conorhastings**][conor]:
255
256```jsx
257import React from 'react'
258import ReactDom from 'react-dom'
259import ReactMarkdown from 'react-markdown'
260import {Prism as SyntaxHighlighter} from 'react-syntax-highlighter'
261import {dark} from 'react-syntax-highlighter/dist/esm/styles/prism'
262
263// Did you know you can use tildes instead of backticks for code in markdown? ✨
264const markdown = `Here is some JavaScript code:
265
266~~~js
267console.log('It works!')
268~~~
269`
270
271ReactDom.render(
272 <ReactMarkdown
273 children={markdown}
274 components={{
275 code({node, inline, className, children, ...props}) {
276 const match = /language-(\w+)/.exec(className || '')
277 return !inline && match ? (
278 <SyntaxHighlighter
279 children={String(children).replace(/\n$/, '')}
280 style={dark}
281 language={match[1]}
282 PreTag="div"
283 {...props}
284 />
285 ) : (
286 <code className={className} {...props}>
287 {children}
288 </code>
289 )
290 }
291 }}
292 />,
293 document.body
294)
295```
296
297<details>
298<summary>Show equivalent JSX</summary>
299
300```jsx
301<>
302 <p>Here is some JavaScript code:</p>
303 <pre>
304 <SyntaxHighlighter language="js" style={dark} PreTag="div" children="console.log('It works!')" />
305 </pre>
306</>
307```
308
309</details>
310
311### Use remark and rehype plugins (math)
312
313This example shows how a syntax extension (through [`remark-math`][math])
314is used to support math in markdown, and a transform plugin
315([`rehype-katex`][katex]) to render that math.
316
317```jsx
318import React from 'react'
319import ReactDom from 'react-dom'
320import ReactMarkdown from 'react-markdown'
321import remarkMath from 'remark-math'
322import rehypeKatex from 'rehype-katex'
323
324import 'katex/dist/katex.min.css' // `rehype-katex` does not import the CSS for you
325
326ReactDom.render(
327 <ReactMarkdown
328 children={`The lift coefficient ($C_L$) is a dimensionless coefficient.`}
329 remarkPlugins={[remarkMath]}
330 rehypePlugins={[rehypeKatex]}
331 />,
332 document.body
333)
334```
335
336<details>
337<summary>Show equivalent JSX</summary>
338
339```jsx
340<p>
341 The lift coefficient (
342 <span className="math math-inline">
343 <span className="katex">
344 <span className="katex-mathml">
345 <math xmlns="http://www.w3.org/1998/Math/MathML">{/* … */}</math>
346 </span>
347 <span className="katex-html" aria-hidden="true">
348 {/* … */}
349 </span>
350 </span>
351 </span>
352 ) is a dimensionless coefficient.
353</p>
354```
355
356</details>
357
358## Architecture
359
360```txt
361 react-markdown
362+-------------------------------------------------------------------------------------------------------------------------------------------+
363| |
364| +----------+ +----------------+ +---------------+ +----------------+ +------------+ |
365| | | | | | | | | | | |
366| -markdown->+ remark +-mdast->+ remark plugins +-mdast->+ remark-rehype +-hast->+ rehype plugins +-hast->+ components +-react elements-> |
367| | | | | | | | | | | |
368| +----------+ +----------------+ +---------------+ +----------------+ +------------+ |
369| |
370+-------------------------------------------------------------------------------------------------------------------------------------------+
371```
372
373relevant links: [markdown](https://commonmark.org), [remark](https://github.com/remarkjs/remark), [mdast](https://github.com/syntax-tree/mdast), [remark plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md), [remark-rehype](https://github.com/remarkjs/remark-rehype), [hast](https://github.com/syntax-tree/hast), [rehype plugins](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md), [components](#appendix-b-components)
374
375To understand what this project does, it’s very important to first understand
376what unified does: please read through the [`unifiedjs/unified`](https://github.com/unifiedjs/unified)
377readme (the part until you hit the API section is required reading).
378
379react-markdown is a unified pipeline — wrapped so that most folks don’t need to
380directly interact with unified. The processor goes through these steps:
381
382* Parse Markdown to mdast (markdown syntax tree)
383* Transform through remark (markdown ecosystem)
384* Transform mdast to hast (HTML syntax tree)
385* Transform through rehype (HTML ecosystem)
386* Render hast to react with components
387
388## Appendix A: HTML in markdown
389
390`react-markdown` typically escapes HTML (or ignores it, with `skipHtml`)
391because it is dangerous and defeats the purpose of this library.
392
393However, if you are in a trusted environment (you trust the markdown), and
394can spare the bundle size (±60kb minzipped), then you can use
395[`rehype-raw`][raw]:
396
397```jsx
398import React from 'react'
399import ReactDom from 'react-dom'
400import ReactMarkdown from 'react-markdown'
401import rehypeRaw from 'rehype-raw'
402
403const input = `<div class="note">
404
405Some *emphasis* and <strong>strong</strong>!
406
407</div>`
408
409ReactDom.render(
410 <ReactMarkdown rehypePlugins={[rehypeRaw]} children={input} />,
411 document.body
412)
413```
414
415<details>
416<summary>Show equivalent JSX</summary>
417
418```jsx
419<div class="note">
420 <p>Some <em>emphasis</em> and <strong>strong</strong>!</p>
421</div>
422```
423
424</details>
425
426**Note**: HTML in markdown is still bound by how [HTML works in
427CommonMark][cm-html].
428Make sure to use blank lines around block-level HTML that again contains
429markdown!
430
431## Appendix B: Components
432
433You can also change the things that come from markdown:
434
435```js
436<ReactMarkdown
437 components={{
438 // Map `h1` (`# heading`) to use `h2`s.
439 h1: 'h2',
440 // Rewrite `em`s (`*like so*`) to `i` with a red foreground color.
441 em: ({node, ...props}) => <i style={{color: 'red'}} {...props} />
442 }}
443/>
444```
445
446The keys in components are HTML equivalents for the things you write with
447markdown (such as `h1` for `# heading`)**†**
448
449**†** Normally, in markdown, those are: `a`, `blockquote`, `code`, `em`, `h1`,
450`h2`, `h3`, `h4`, `h5`, `h6`, `hr`, `img`, `li`, `ol`, `p`, `pre`, `strong`, and
451`ul`.
452With [`remark-gfm`][gfm], you can also use: `del`, `input`, `table`, `tbody`,
453`td`, `th`, `thead`, and `tr`.
454Other remark or rehype plugins that add support for new constructs will also
455work with `react-markdown`.
456
457The props that are passed are what you probably would expect: an `a` (link) will
458get `href` (and `title`) props, and `img` (image) an `src` (and `title`), etc.
459There are some extra props passed.
460
461* `code`
462 * `inline` (`boolean?`)
463 — set to `true` for inline code
464 * `className` (`string?`)
465 — set to `language-js` or so when using ` ```js `
466* `h1`, `h2`, `h3`, `h4`, `h5`, `h6`
467 * `level` (`number` beween 1 and 6)
468 — heading rank
469* `input` (when using [`remark-gfm`][gfm])
470 * `checked` (`boolean`)
471 — whether the item is checked
472 * `disabled` (`true`)
473 * `type` (`'checkbox'`)
474* `li`
475 * `index` (`number`)
476 — number of preceding items (so first gets `0`, etc.)
477 * `ordered` (`boolean`)
478 — whether the parent is an `ol` or not
479 * `checked` (`boolean?`)
480 — `null` normally, `boolean` when using [`remark-gfm`][gfm]’s tasklists
481 * `className` (`string?`)
482 — set to `task-list-item` when using [`remark-gfm`][gfm] and the
483 item1 is a tasklist
484* `ol`, `ul`
485 * `depth` (`number`)
486 — number of ancestral lists (so first gets `0`, etc.)
487 * `ordered` (`boolean`)
488 — whether it’s an `ol` or not
489 * `className` (`string?`)
490 — set to `contains-task-list` when using [`remark-gfm`][gfm] and the
491 list contains one or more tasklists
492* `td`, `th` (when using [`remark-gfm`][gfm])
493 * `style` (`Object?`)
494 — something like `{textAlign: 'left'}` depending on how the cell is
495 aligned
496 * `isHeader` (`boolean`)
497 — whether it’s a `th` or not
498* `tr` (when using [`remark-gfm`][gfm])
499 * `isHeader` (`boolean`)
500 — whether it’s in the `thead` or not
501
502Every component will receive a `node` (`Object`).
503This is the original [hast](https://github.com/syntax-tree/hast) element being
504turned into a React element.
505
506Every element will receive a `key` (`string`).
507See [React’s docs](https://reactjs.org/docs/lists-and-keys.html#keys) for more
508info.
509
510Optionally, components will also receive:
511
512* `data-sourcepos` (`string`)
513 — see `sourcePos` option
514* `sourcePosition` (`Object`)
515 — see `rawSourcePos` option
516* `index` and `siblingCount` (`number`)
517 — see `includeElementIndex` option
518* `target` on `a` (`string`)
519 — see `linkTarget` option
520
521## Security
522
523Use of `react-markdown` is secure by default.
524Overwriting `transformLinkUri` or `transformImageUri` to something insecure will
525open you up to XSS vectors.
526Furthermore, the `remarkPlugins` and `rehypePlugins` you use and `components`
527you write may be insecure.
528
529To make sure the content is completely safe, even after what plugins do,
530use [`rehype-sanitize`][sanitize].
531That plugin lets you define your own schema of what is and isn’t allowed.
532
533## Related
534
535* [`MDX`](https://github.com/mdx-js/mdx)
536 — JSX *in* markdown
537* [`remark-gfm`](https://github.com/remarkjs/remark-gfm)
538 — Plugin for GitHub flavored markdown support
539
540## Contribute
541
542See [`contributing.md`][contributing] in [`remarkjs/.github`][health] for ways
543to get started.
544See [`support.md`][support] for ways to get help.
545
546This project has a [code of conduct][coc].
547By interacting with this repository, organization, or community you agree to
548abide by its terms.
549
550## License
551
552[MIT][license] © [Espen Hovlandsdal][author]
553
554[build-badge]: https://github.com/remarkjs/react-markdown/workflows/main/badge.svg
555
556[build]: https://github.com/remarkjs/react-markdown/actions
557
558[coverage-badge]: https://img.shields.io/codecov/c/github/remarkjs/react-markdown.svg
559
560[coverage]: https://codecov.io/github/remarkjs/react-markdown
561
562[downloads-badge]: https://img.shields.io/npm/dm/react-markdown.svg
563
564[downloads]: https://www.npmjs.com/package/react-markdown
565
566[size-badge]: https://img.shields.io/bundlephobia/minzip/react-markdown.svg
567
568[size]: https://bundlephobia.com/result?p=react-markdown
569
570[sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg
571
572[backers-badge]: https://opencollective.com/unified/backers/badge.svg
573
574[collective]: https://opencollective.com/unified
575
576[chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg
577
578[chat]: https://github.com/remarkjs/remark/discussions
579
580[npm]: https://docs.npmjs.com/cli/install
581
582[health]: https://github.com/remarkjs/.github
583
584[contributing]: https://github.com/remarkjs/.github/blob/HEAD/contributing.md
585
586[support]: https://github.com/remarkjs/.github/blob/HEAD/support.md
587
588[coc]: https://github.com/remarkjs/.github/blob/HEAD/code-of-conduct.md
589
590[license]: license
591
592[author]: https://espen.codes/
593
594[remark]: https://github.com/remarkjs/remark
595
596[demo]: https://remarkjs.github.io/react-markdown/
597
598[learn]: https://commonmark.org/help/
599
600[position]: https://github.com/syntax-tree/unist#position
601
602[gfm]: https://github.com/remarkjs/remark-gfm
603
604[math]: https://github.com/remarkjs/remark-math
605
606[katex]: https://github.com/remarkjs/remark-math/tree/main/packages/rehype-katex
607
608[raw]: https://github.com/rehypejs/rehype-raw
609
610[sanitize]: https://github.com/rehypejs/rehype-sanitize
611
612[remark-plugins]: https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins
613
614[rehype-plugins]: https://github.com/rehypejs/rehype/blob/main/doc/plugins.md#list-of-plugins
615
616[cm-html]: https://spec.commonmark.org/0.29/#html-blocks
617
618[uri]: https://github.com/remarkjs/react-markdown/blob/main/lib/uri-transformer.js
619
620[security]: #security
621
622[react-syntax-highlighter]: https://github.com/react-syntax-highlighter/react-syntax-highlighter
623
624[conor]: https://github.com/conorhastings