1 |
|
2 | Notes for maintaining this document:
|
3 |
|
4 | * Update the link for `cm-html` once in a while
|
5 | -->
|
6 |
|
7 | # react-markdown
|
8 |
|
9 | [![Build][build-badge]][build]
|
10 | [![Coverage][coverage-badge]][coverage]
|
11 | [![Downloads][downloads-badge]][downloads]
|
12 | [![Size][size-badge]][size]
|
13 | [![Sponsors][sponsors-badge]][collective]
|
14 | [![Backers][backers-badge]][collective]
|
15 | [![Chat][chat-badge]][chat]
|
16 |
|
17 | React component to render markdown.
|
18 |
|
19 | ## Feature highlights
|
20 |
|
21 | * [x] **[safe][section-security] by default**
|
22 | (no `dangerouslySetInnerHTML` or XSS attacks)
|
23 | * [x] **[components][section-components]**
|
24 | (pass your own component to use instead of `<h2>` for `## hi`)
|
25 | * [x] **[plugins][section-plugins]**
|
26 | (many plugins you can pick and choose from)
|
27 | * [x] **[compliant][section-syntax]**
|
28 | (100% to CommonMark, 100% to GFM with a plugin)
|
29 |
|
30 | ## Contents
|
31 |
|
32 | * [What is this?](#what-is-this)
|
33 | * [When should I use this?](#when-should-i-use-this)
|
34 | * [Install](#install)
|
35 | * [Use](#use)
|
36 | * [API](#api)
|
37 | * [`Markdown`](#markdown)
|
38 | * [`defaultUrlTransform(url)`](#defaulturltransformurl)
|
39 | * [`AllowElement`](#allowelement)
|
40 | * [`Components`](#components)
|
41 | * [`ExtraProps`](#extraprops)
|
42 | * [`Options`](#options)
|
43 | * [`UrlTransform`](#urltransform)
|
44 | * [Examples](#examples)
|
45 | * [Use a plugin](#use-a-plugin)
|
46 | * [Use a plugin with options](#use-a-plugin-with-options)
|
47 | * [Use custom components (syntax highlight)](#use-custom-components-syntax-highlight)
|
48 | * [Use remark and rehype plugins (math)](#use-remark-and-rehype-plugins-math)
|
49 | * [Plugins](#plugins)
|
50 | * [Syntax](#syntax)
|
51 | * [Types](#types)
|
52 | * [Compatibility](#compatibility)
|
53 | * [Architecture](#architecture)
|
54 | * [Appendix A: HTML in markdown](#appendix-a-html-in-markdown)
|
55 | * [Appendix B: Components](#appendix-b-components)
|
56 | * [Appendix C: line endings in markdown (and JSX)](#appendix-c-line-endings-in-markdown-and-jsx)
|
57 | * [Security](#security)
|
58 | * [Related](#related)
|
59 | * [Contribute](#contribute)
|
60 | * [License](#license)
|
61 |
|
62 | ## What is this?
|
63 |
|
64 | This package is a [React][] component that can be given a string of markdown
|
65 | that it’ll safely render to React elements.
|
66 | You can pass plugins to change how markdown is transformed and pass components
|
67 | that will be used instead of normal HTML elements.
|
68 |
|
69 | * to learn markdown, see this [cheatsheet and tutorial][commonmark-help]
|
70 | * to try out `react-markdown`, see [our demo][demo]
|
71 |
|
72 | ## When should I use this?
|
73 |
|
74 | There are other ways to use markdown in React out there so why use this one?
|
75 | The three main reasons are that they often rely on `dangerouslySetInnerHTML`,
|
76 | have bugs with how they handle markdown, or don’t let you swap elements for
|
77 | components.
|
78 | `react-markdown` builds a virtual DOM, so React only replaces what changed,
|
79 | from a syntax tree.
|
80 | That’s supported because we use [unified][], specifically [remark][] for
|
81 | markdown and [rehype][] for HTML, which are popular tools to transform content
|
82 | with plugins.
|
83 |
|
84 | This package focusses on making it easy for beginners to safely use markdown in
|
85 | React.
|
86 | When you’re familiar with unified, you can use a modern hooks based alternative
|
87 | [`react-remark`][react-remark] or [`rehype-react`][rehype-react] manually.
|
88 | If you instead want to use JavaScript and JSX *inside* markdown files, use
|
89 | [MDX][].
|
90 |
|
91 | ## Install
|
92 |
|
93 | This package is [ESM only][esm].
|
94 | In Node.js (version 16+), install with [npm][]:
|
95 |
|
96 | ```sh
|
97 | npm install react-markdown
|
98 | ```
|
99 |
|
100 | In Deno with [`esm.sh`][esmsh]:
|
101 |
|
102 | ```js
|
103 | import Markdown from 'https://esm.sh/react-markdown@9'
|
104 | ```
|
105 |
|
106 | In browsers with [`esm.sh`][esmsh]:
|
107 |
|
108 | ```html
|
109 | <script type="module">
|
110 | import Markdown from 'https://esm.sh/react-markdown@9?bundle'
|
111 | </script>
|
112 | ```
|
113 |
|
114 | ## Use
|
115 |
|
116 | A basic hello world:
|
117 |
|
118 | ```jsx
|
119 | import React from 'react'
|
120 | import {createRoot} from 'react-dom/client'
|
121 | import Markdown from 'react-markdown'
|
122 |
|
123 | const markdown = '# Hi, *Pluto*!'
|
124 |
|
125 | createRoot(document.body).render(<Markdown>{markdown}</Markdown>)
|
126 | ```
|
127 |
|
128 | <details>
|
129 | <summary>Show equivalent JSX</summary>
|
130 |
|
131 | ```jsx
|
132 | <h1>
|
133 | Hi, <em>Pluto</em>!
|
134 | </h1>
|
135 | ```
|
136 |
|
137 | </details>
|
138 |
|
139 | Here is an example that shows how to use a plugin ([`remark-gfm`][remark-gfm],
|
140 | which adds support for footnotes, strikethrough, tables, tasklists and URLs
|
141 | directly):
|
142 |
|
143 | ```jsx
|
144 | import React from 'react'
|
145 | import {createRoot} from 'react-dom/client'
|
146 | import Markdown from 'react-markdown'
|
147 | import remarkGfm from 'remark-gfm'
|
148 |
|
149 | const markdown = `Just a link: www.nasa.gov.`
|
150 |
|
151 | createRoot(document.body).render(
|
152 | <Markdown remarkPlugins={[remarkGfm]}>{markdown}</Markdown>
|
153 | )
|
154 | ```
|
155 |
|
156 | <details>
|
157 | <summary>Show equivalent JSX</summary>
|
158 |
|
159 | ```jsx
|
160 | <p>
|
161 | Just a link: <a href="http://www.nasa.gov">www.nasa.gov</a>.
|
162 | </p>
|
163 | ```
|
164 |
|
165 | </details>
|
166 |
|
167 | ## API
|
168 |
|
169 | This package exports the following identifier:
|
170 | [`defaultUrlTransform`][api-default-url-transform].
|
171 | The default export is [`Markdown`][api-markdown].
|
172 |
|
173 | ### `Markdown`
|
174 |
|
175 | Component to render markdown.
|
176 |
|
177 | ###### Parameters
|
178 |
|
179 | * `options` ([`Options`][api-options])
|
180 | — props
|
181 |
|
182 | ###### Returns
|
183 |
|
184 | React element (`JSX.Element`).
|
185 |
|
186 | ### `defaultUrlTransform(url)`
|
187 |
|
188 | Make a URL safe.
|
189 |
|
190 | ###### Parameters
|
191 |
|
192 | * `url` (`string`)
|
193 | — URL
|
194 |
|
195 | ###### Returns
|
196 |
|
197 | Safe URL (`string`).
|
198 |
|
199 | ### `AllowElement`
|
200 |
|
201 | Filter elements (TypeScript type).
|
202 |
|
203 | ###### Parameters
|
204 |
|
205 | * `node` ([`Element` from `hast`][hast-element])
|
206 | — element to check
|
207 | * `index` (`number | undefined`)
|
208 | — index of `element` in `parent`
|
209 | * `parent` ([`Node` from `hast`][hast-node])
|
210 | — parent of `element`
|
211 |
|
212 | ###### Returns
|
213 |
|
214 | Whether to allow `element` (`boolean`, optional).
|
215 |
|
216 | ### `Components`
|
217 |
|
218 | Map tag names to components (TypeScript type).
|
219 |
|
220 | ###### Type
|
221 |
|
222 | ```ts
|
223 | import type {Element} from 'hast'
|
224 |
|
225 | type Components = Partial<{
|
226 | [TagName in keyof JSX.IntrinsicElements]:
|
227 | // Class component:
|
228 | | (new (props: JSX.IntrinsicElements[TagName] & ExtraProps) => JSX.ElementClass)
|
229 | // Function component:
|
230 | | ((props: JSX.IntrinsicElements[TagName] & ExtraProps) => JSX.Element | string | null | undefined)
|
231 | // Tag name:
|
232 | | keyof JSX.IntrinsicElements
|
233 | }>
|
234 | ```
|
235 |
|
236 | ### `ExtraProps`
|
237 |
|
238 | Extra fields we pass to components (TypeScript type).
|
239 |
|
240 | ###### Fields
|
241 |
|
242 | * `node` ([`Element` from `hast`][hast-element], optional)
|
243 | — original node
|
244 |
|
245 | ### `Options`
|
246 |
|
247 | Configuration (TypeScript type).
|
248 |
|
249 | ###### Fields
|
250 |
|
251 | * `allowElement` ([`AllowElement`][api-allow-element], optional)
|
252 | — filter elements;
|
253 | `allowedElements` / `disallowedElements` is used first
|
254 | * `allowedElements` (`Array<string>`, default: all tag names)
|
255 | — tag names to allow;
|
256 | cannot combine w/ `disallowedElements`
|
257 | * `children` (`string`, optional)
|
258 | — markdown
|
259 | * `className` (`string`, optional)
|
260 | — wrap in a `div` with this class name
|
261 | * `components` ([`Components`][api-components], optional)
|
262 | — map tag names to components
|
263 | * `disallowedElements` (`Array<string>`, default: `[]`)
|
264 | — tag names to disallow;
|
265 | cannot combine w/ `allowedElements`
|
266 | * `rehypePlugins` (`Array<Plugin>`, optional)
|
267 | — list of [rehype plugins][rehype-plugins] to use
|
268 | * `remarkPlugins` (`Array<Plugin>`, optional)
|
269 | — list of [remark plugins][remark-plugins] to use
|
270 | * `remarkRehypeOptions` ([`Options` from
|
271 | `remark-rehype`][remark-rehype-options], optional)
|
272 | — options to pass through to `remark-rehype`
|
273 | * `skipHtml` (`boolean`, default: `false`)
|
274 | — ignore HTML in markdown completely
|
275 | * `unwrapDisallowed` (`boolean`, default: `false`)
|
276 | — extract (unwrap) what’s in disallowed elements;
|
277 | normally when say `strong` is not allowed, it and it’s children are dropped,
|
278 | with `unwrapDisallowed` the element itself is replaced by its children
|
279 | * `urlTransform` ([`UrlTransform`][api-url-transform], default:
|
280 | [`defaultUrlTransform`][api-default-url-transform])
|
281 | — change URLs
|
282 |
|
283 | ### `UrlTransform`
|
284 |
|
285 | Transform URLs (TypeScript type).
|
286 |
|
287 | ###### Parameters
|
288 |
|
289 | * `url` (`string`)
|
290 | — URL
|
291 | * `key` (`string`, example: `'href'`)
|
292 | — property name
|
293 | * `node` ([`Element` from `hast`][hast-element])
|
294 | — element to check
|
295 |
|
296 | ###### Returns
|
297 |
|
298 | Transformed URL (`string`, optional).
|
299 |
|
300 | ## Examples
|
301 |
|
302 | ### Use a plugin
|
303 |
|
304 | This example shows how to use a remark plugin.
|
305 | In this case, [`remark-gfm`][remark-gfm], which adds support for strikethrough,
|
306 | tables, tasklists and URLs directly:
|
307 |
|
308 | ```jsx
|
309 | import React from 'react'
|
310 | import {createRoot} from 'react-dom/client'
|
311 | import Markdown from 'react-markdown'
|
312 | import remarkGfm from 'remark-gfm'
|
313 |
|
314 | const markdown = `A paragraph with *emphasis* and **strong importance**.
|
315 |
|
316 | > A block quote with ~strikethrough~ and a URL: https://reactjs.org.
|
317 |
|
318 | * Lists
|
319 | * [ ] todo
|
320 | * [x] done
|
321 |
|
322 | A table:
|
323 |
|
324 | | a | b |
|
325 | | - | - |
|
326 | `
|
327 |
|
328 | createRoot(document.body).render(
|
329 | <Markdown remarkPlugins={[remarkGfm]}>{markdown}</Markdown>
|
330 | )
|
331 | ```
|
332 |
|
333 | <details>
|
334 | <summary>Show equivalent JSX</summary>
|
335 |
|
336 | ```jsx
|
337 | <>
|
338 | <p>
|
339 | A paragraph with <em>emphasis</em> and <strong>strong importance</strong>.
|
340 | </p>
|
341 | <blockquote>
|
342 | <p>
|
343 | A block quote with <del>strikethrough</del> and a URL:{' '}
|
344 | <a href="https://reactjs.org">https://reactjs.org</a>.
|
345 | </p>
|
346 | </blockquote>
|
347 | <ul className="contains-task-list">
|
348 | <li>Lists</li>
|
349 | <li className="task-list-item">
|
350 | <input type="checkbox" disabled /> todo
|
351 | </li>
|
352 | <li className="task-list-item">
|
353 | <input type="checkbox" disabled checked /> done
|
354 | </li>
|
355 | </ul>
|
356 | <p>A table:</p>
|
357 | <table>
|
358 | <thead>
|
359 | <tr>
|
360 | <th>a</th>
|
361 | <th>b</th>
|
362 | </tr>
|
363 | </thead>
|
364 | </table>
|
365 | </>
|
366 | ```
|
367 |
|
368 | </details>
|
369 |
|
370 | ### Use a plugin with options
|
371 |
|
372 | This example shows how to use a plugin and give it options.
|
373 | To do that, use an array with the plugin at the first place, and the options
|
374 | second.
|
375 | [`remark-gfm`][remark-gfm] has an option to allow only double tildes for
|
376 | strikethrough:
|
377 |
|
378 | ```jsx
|
379 | import React from 'react'
|
380 | import {createRoot} from 'react-dom/client'
|
381 | import Markdown from 'react-markdown'
|
382 | import remarkGfm from 'remark-gfm'
|
383 |
|
384 | const markdown = 'This ~is not~ strikethrough, but ~~this is~~!'
|
385 |
|
386 | createRoot(document.body).render(
|
387 | <Markdown remarkPlugins={[[remarkGfm, {singleTilde: false}]]}>
|
388 | {markdown}
|
389 | </Markdown>
|
390 | )
|
391 | ```
|
392 |
|
393 | <details>
|
394 | <summary>Show equivalent JSX</summary>
|
395 |
|
396 | ```jsx
|
397 | <p>
|
398 | This ~is not~ strikethrough, but <del>this is</del>!
|
399 | </p>
|
400 | ```
|
401 |
|
402 | </details>
|
403 |
|
404 | ### Use custom components (syntax highlight)
|
405 |
|
406 | This example shows how you can overwrite the normal handling of an element by
|
407 | passing a component.
|
408 | In this case, we apply syntax highlighting with the seriously super amazing
|
409 | [`react-syntax-highlighter`][react-syntax-highlighter] by
|
410 | [**@conorhastings**][conor]:
|
411 |
|
412 |
|
413 |
|
414 | ```jsx
|
415 | import React from 'react'
|
416 | import {createRoot} from 'react-dom/client'
|
417 | import Markdown from 'react-markdown'
|
418 | import {Prism as SyntaxHighlighter} from 'react-syntax-highlighter'
|
419 | import {dark} from 'react-syntax-highlighter/dist/esm/styles/prism'
|
420 |
|
421 | // Did you know you can use tildes instead of backticks for code in markdown? ✨
|
422 | const markdown = `Here is some JavaScript code:
|
423 |
|
424 | ~~~js
|
425 | console.log('It works!')
|
426 | ~~~
|
427 | `
|
428 |
|
429 | createRoot(document.body).render(
|
430 | <Markdown
|
431 | children={markdown}
|
432 | components={{
|
433 | code(props) {
|
434 | const {children, className, node, ...rest} = props
|
435 | const match = /language-(\w+)/.exec(className || '')
|
436 | return match ? (
|
437 | <SyntaxHighlighter
|
438 | {...rest}
|
439 | PreTag="div"
|
440 | children={String(children).replace(/\n$/, '')}
|
441 | language={match[1]}
|
442 | style={dark}
|
443 | />
|
444 | ) : (
|
445 | <code {...rest} className={className}>
|
446 | {children}
|
447 | </code>
|
448 | )
|
449 | }
|
450 | }}
|
451 | />
|
452 | )
|
453 | ```
|
454 |
|
455 | <details>
|
456 | <summary>Show equivalent JSX</summary>
|
457 |
|
458 | ```jsx
|
459 | <>
|
460 | <p>Here is some JavaScript code:</p>
|
461 | <pre>
|
462 | <SyntaxHighlighter language="js" style={dark} PreTag="div" children="console.log('It works!')" />
|
463 | </pre>
|
464 | </>
|
465 | ```
|
466 |
|
467 | </details>
|
468 |
|
469 | ### Use remark and rehype plugins (math)
|
470 |
|
471 | This example shows how a syntax extension (through [`remark-math`][remark-math])
|
472 | is used to support math in markdown, and a transform plugin
|
473 | ([`rehype-katex`][rehype-katex]) to render that math.
|
474 |
|
475 | ```jsx
|
476 | import React from 'react'
|
477 | import {createRoot} from 'react-dom/client'
|
478 | import Markdown from 'react-markdown'
|
479 | import rehypeKatex from 'rehype-katex'
|
480 | import remarkMath from 'remark-math'
|
481 | import 'katex/dist/katex.min.css' // `rehype-katex` does not import the CSS for you
|
482 |
|
483 | const markdown = `The lift coefficient ($C_L$) is a dimensionless coefficient.`
|
484 |
|
485 | createRoot(document.body).render(
|
486 | <Markdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
|
487 | {markdown}
|
488 | </Markdown>
|
489 | )
|
490 | ```
|
491 |
|
492 | <details>
|
493 | <summary>Show equivalent JSX</summary>
|
494 |
|
495 | ```jsx
|
496 | <p>
|
497 | The lift coefficient (
|
498 | <span className="katex">
|
499 | <span className="katex-mathml">
|
500 | <math xmlns="http://www.w3.org/1998/Math/MathML">{/* … */}</math>
|
501 | </span>
|
502 | <span className="katex-html" aria-hidden="true">
|
503 | {/* … */}
|
504 | </span>
|
505 | </span>
|
506 | ) is a dimensionless coefficient.
|
507 | </p>
|
508 | ```
|
509 |
|
510 | </details>
|
511 |
|
512 | ## Plugins
|
513 |
|
514 | We use [unified][], specifically [remark][] for markdown and [rehype][] for
|
515 | HTML, which are tools to transform content with plugins.
|
516 | Here are three good ways to find plugins:
|
517 |
|
518 | * [`awesome-remark`][awesome-remark] and [`awesome-rehype`][awesome-rehype]
|
519 | — selection of the most awesome projects
|
520 | * [List of remark plugins][remark-plugins] and
|
521 | [list of rehype plugins][rehype-plugins]
|
522 | — list of all plugins
|
523 | * [`remark-plugin`][remark-plugin] and [`rehype-plugin`][rehype-plugin] topics
|
524 | — any tagged repo on GitHub
|
525 |
|
526 | ## Syntax
|
527 |
|
528 | `react-markdown` follows CommonMark, which standardizes the differences between
|
529 | markdown implementations, by default.
|
530 | Some syntax extensions are supported through plugins.
|
531 |
|
532 | We use [`micromark`][micromark] under the hood for our parsing.
|
533 | See its documentation for more information on markdown, CommonMark, and
|
534 | extensions.
|
535 |
|
536 | ## Types
|
537 |
|
538 | This package is fully typed with [TypeScript][].
|
539 | It exports the additional types
|
540 | [`AllowElement`][api-allow-element],
|
541 | [`ExtraProps`][api-extra-props],
|
542 | [`Components`][api-components],
|
543 | [`Options`][api-options], and
|
544 | [`UrlTransform`][api-url-transform].
|
545 |
|
546 | ## Compatibility
|
547 |
|
548 | Projects maintained by the unified collective are compatible with maintained
|
549 | versions of Node.js.
|
550 |
|
551 | When we cut a new major release, we drop support for unmaintained versions of
|
552 | Node.
|
553 | This means we try to keep the current release line, `react-markdown@^9`,
|
554 | compatible with Node.js 16.
|
555 |
|
556 | They work in all modern browsers (essentially: everything not IE 11).
|
557 | You can use a bundler (such as esbuild, webpack, or Rollup) to use this package
|
558 | in your project, and use its options (or plugins) to add support for legacy
|
559 | browsers.
|
560 |
|
561 | ## Architecture
|
562 |
|
563 | <pre><code> react-markdown
|
564 | +----------------------------------------------------------------------------------------------------------------+
|
565 | | |
|
566 | | +----------+ +----------------+ +---------------+ +----------------+ +------------+ |
|
567 | | | | | | | | | | | | |
|
568 | <a href="https://commonmark.org">markdown</a>-+->+ <a href="https://github.com/remarkjs/remark">remark</a> +-<a href="https://github.com/syntax-tree/mdast">mdast</a>->+ <a href="https://github.com/remarkjs/remark/blob/main/doc/plugins.md">remark plugins</a> +-<a href="https://github.com/syntax-tree/mdast">mdast</a>->+ <a href="https://github.com/remarkjs/remark-rehype">remark-rehype</a> +-<a href="https://github.com/syntax-tree/hast">hast</a>->+ <a href="https://github.com/rehypejs/rehype/blob/main/doc/plugins.md">rehype plugins</a> +-<a href="https://github.com/syntax-tree/hast">hast</a>->+ <a href="#appendix-b-components">components</a> +-+->react elements
|
569 | | | | | | | | | | | | |
|
570 | | +----------+ +----------------+ +---------------+ +----------------+ +------------+ |
|
571 | | |
|
572 | +----------------------------------------------------------------------------------------------------------------+
|
573 | </code></pre>
|
574 |
|
575 | To understand what this project does, it’s important to first understand what
|
576 | unified does: please read through the [`unifiedjs/unified`][unified] readme (the
|
577 | part until you hit the API section is required reading).
|
578 |
|
579 | `react-markdown` is a unified pipeline — wrapped so that most folks don’t need
|
580 | to directly interact with unified.
|
581 | The processor goes through these steps:
|
582 |
|
583 | * parse markdown to mdast (markdown syntax tree)
|
584 | * transform through remark (markdown ecosystem)
|
585 | * transform mdast to hast (HTML syntax tree)
|
586 | * transform through rehype (HTML ecosystem)
|
587 | * render hast to React with components
|
588 |
|
589 | ## Appendix A: HTML in markdown
|
590 |
|
591 | `react-markdown` typically escapes HTML (or ignores it, with `skipHtml`)
|
592 | because it is dangerous and defeats the purpose of this library.
|
593 |
|
594 | However, if you are in a trusted environment (you trust the markdown), and
|
595 | can spare the bundle size (±60kb minzipped), then you can use
|
596 | [`rehype-raw`][rehype-raw]:
|
597 |
|
598 | ```jsx
|
599 | import React from 'react'
|
600 | import {createRoot} from 'react-dom/client'
|
601 | import Markdown from 'react-markdown'
|
602 | import rehypeRaw from 'rehype-raw'
|
603 |
|
604 | const markdown = `<div class="note">
|
605 |
|
606 | Some *emphasis* and <strong>strong</strong>!
|
607 |
|
608 | </div>`
|
609 |
|
610 | createRoot(document.body).render(
|
611 | <Markdown rehypePlugins={[rehypeRaw]}>{markdown}</Markdown>
|
612 | )
|
613 | ```
|
614 |
|
615 | <details>
|
616 | <summary>Show equivalent JSX</summary>
|
617 |
|
618 | ```jsx
|
619 | <div className="note">
|
620 | <p>
|
621 | Some <em>emphasis</em> and <strong>strong</strong>!
|
622 | </p>
|
623 | </div>
|
624 | ```
|
625 |
|
626 | </details>
|
627 |
|
628 | **Note**: HTML in markdown is still bound by how [HTML works in
|
629 | CommonMark][commonmark-html].
|
630 | Make sure to use blank lines around block-level HTML that again contains
|
631 | markdown!
|
632 |
|
633 | ## Appendix B: Components
|
634 |
|
635 | You can also change the things that come from markdown:
|
636 |
|
637 | ```jsx
|
638 | <Markdown
|
639 | components={{
|
640 | // Map `h1` (`# heading`) to use `h2`s.
|
641 | h1: 'h2',
|
642 | // Rewrite `em`s (`*like so*`) to `i` with a red foreground color.
|
643 | em(props) {
|
644 | const {node, ...rest} = props
|
645 | return <i style={{color: 'red'}} {...rest} />
|
646 | }
|
647 | }}
|
648 | />
|
649 | ```
|
650 |
|
651 | The keys in components are HTML equivalents for the things you write with
|
652 | markdown (such as `h1` for `# heading`).
|
653 | Normally, in markdown, those are: `a`, `blockquote`, `br`, `code`, `em`, `h1`,
|
654 | `h2`, `h3`, `h4`, `h5`, `h6`, `hr`, `img`, `li`, `ol`, `p`, `pre`, `strong`, and
|
655 | `ul`.
|
656 | With [`remark-gfm`][remark-gfm], you can also use `del`, `input`, `table`,
|
657 | `tbody`, `td`, `th`, `thead`, and `tr`.
|
658 | Other remark or rehype plugins that add support for new constructs will also
|
659 | work with `react-markdown`.
|
660 |
|
661 | The props that are passed are what you probably would expect: an `a` (link) will
|
662 | get `href` (and `title`) props, and `img` (image) an `src`, `alt` and `title`,
|
663 | etc.
|
664 |
|
665 | Every component will receive a `node`.
|
666 | This is the original [`Element` from `hast`][hast-element] element being turned
|
667 | into a React element.
|
668 |
|
669 | ## Appendix C: line endings in markdown (and JSX)
|
670 |
|
671 | You might have trouble with how line endings work in markdown and JSX.
|
672 | We recommend the following, which solves all line ending problems:
|
673 |
|
674 | ```jsx
|
675 | // If you write actual markdown in your code, put your markdown in a variable;
|
676 | // **do not indent markdown**:
|
677 | const markdown = `
|
678 | # This is perfect!
|
679 | `
|
680 |
|
681 | // Pass the value as an expresion as an only child:
|
682 | const result = <Markdown>{markdown}</Markdown>
|
683 | ```
|
684 |
|
685 | 👆 That works.
|
686 | Read on for what doesn’t and why that is.
|
687 |
|
688 | You might try to write markdown directly in your JSX and find that it **does
|
689 | not** work:
|
690 |
|
691 | ```jsx
|
692 | <Markdown>
|
693 | # Hi
|
694 |
|
695 | This is **not** a paragraph.
|
696 | </Markdown>
|
697 | ```
|
698 |
|
699 | The is because in JSX the whitespace (including line endings) is collapsed to
|
700 | a single space.
|
701 | So the above example is equivalent to:
|
702 |
|
703 | ```jsx
|
704 | <Markdown> # Hi This is **not** a paragraph. </Markdown>
|
705 | ```
|
706 |
|
707 | Instead, to pass markdown to `Markdown`, you can use an expression:
|
708 | with a template literal:
|
709 |
|
710 | ```jsx
|
711 | <Markdown>{`
|
712 | # Hi
|
713 |
|
714 | This is a paragraph.
|
715 | `}</Markdown>
|
716 | ```
|
717 |
|
718 | Template literals have another potential problem, because they keep whitespace
|
719 | (including indentation) inside them.
|
720 | That means that the following **does not** turn into a heading:
|
721 |
|
722 | ```jsx
|
723 | <Markdown>{`
|
724 | # This is **not** a heading, it’s an indented code block
|
725 | `}</Markdown>
|
726 | ```
|
727 |
|
728 | ## Security
|
729 |
|
730 | Use of `react-markdown` is secure by default.
|
731 | Overwriting `urlTransform` to something insecure will open you up to XSS
|
732 | vectors.
|
733 | Furthermore, the `remarkPlugins`, `rehypePlugins`, and `components` you use may
|
734 | be insecure.
|
735 |
|
736 | To make sure the content is completely safe, even after what plugins do,
|
737 | use [`rehype-sanitize`][rehype-sanitize].
|
738 | It lets you define your own schema of what is and isn’t allowed.
|
739 |
|
740 | ## Related
|
741 |
|
742 | * [`MDX`][mdx]
|
743 | — JSX *in* markdown
|
744 | * [`remark-gfm`][remark-gfm]
|
745 | — add support for GitHub flavored markdown support
|
746 | * [`react-remark`][react-remark]
|
747 | — hook based alternative
|
748 | * [`rehype-react`][rehype-react]
|
749 | — turn HTML into React elements
|
750 |
|
751 | ## Contribute
|
752 |
|
753 | See [`contributing.md`][contributing] in [`remarkjs/.github`][health] for ways
|
754 | to get started.
|
755 | See [`support.md`][support] for ways to get help.
|
756 |
|
757 | This project has a [code of conduct][coc].
|
758 | By interacting with this repository, organization, or community you agree to
|
759 | abide by its terms.
|
760 |
|
761 | ## License
|
762 |
|
763 | [MIT][license] © [Espen Hovlandsdal][author]
|
764 |
|
765 | [build-badge]: https://github.com/remarkjs/react-markdown/workflows/main/badge.svg
|
766 |
|
767 | [build]: https://github.com/remarkjs/react-markdown/actions
|
768 |
|
769 | [coverage-badge]: https://img.shields.io/codecov/c/github/remarkjs/react-markdown.svg
|
770 |
|
771 | [coverage]: https://codecov.io/github/remarkjs/react-markdown
|
772 |
|
773 | [downloads-badge]: https://img.shields.io/npm/dm/react-markdown.svg
|
774 |
|
775 | [downloads]: https://www.npmjs.com/package/react-markdown
|
776 |
|
777 | [size-badge]: https://img.shields.io/bundlejs/size/react-markdown
|
778 |
|
779 | [size]: https://bundlejs.com/?q=react-markdown
|
780 |
|
781 | [sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg
|
782 |
|
783 | [backers-badge]: https://opencollective.com/unified/backers/badge.svg
|
784 |
|
785 | [collective]: https://opencollective.com/unified
|
786 |
|
787 | [chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg
|
788 |
|
789 | [chat]: https://github.com/remarkjs/remark/discussions
|
790 |
|
791 | [npm]: https://docs.npmjs.com/cli/install
|
792 |
|
793 | [esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
|
794 |
|
795 | [esmsh]: https://esm.sh
|
796 |
|
797 | [health]: https://github.com/remarkjs/.github
|
798 |
|
799 | [coc]: https://github.com/remarkjs/.github/blob/main/code-of-conduct.md
|
800 |
|
801 | [contributing]: https://github.com/remarkjs/.github/blob/main/contributing.md
|
802 |
|
803 | [support]: https://github.com/remarkjs/.github/blob/main/support.md
|
804 |
|
805 | [license]: license
|
806 |
|
807 | [author]: https://espen.codes/
|
808 |
|
809 | [awesome-remark]: https://github.com/remarkjs/awesome-remark
|
810 |
|
811 | [awesome-rehype]: https://github.com/rehypejs/awesome-rehype
|
812 |
|
813 | [commonmark-help]: https://commonmark.org/help/
|
814 |
|
815 | [commonmark-html]: https://spec.commonmark.org/0.30/#html-blocks
|
816 |
|
817 | [hast-element]: https://github.com/syntax-tree/hast#element
|
818 |
|
819 | [hast-node]: https://github.com/syntax-tree/hast#nodes
|
820 |
|
821 | [mdx]: https://github.com/mdx-js/mdx/
|
822 |
|
823 | [micromark]: https://github.com/micromark/micromark
|
824 |
|
825 | [react]: http://reactjs.org
|
826 |
|
827 | [react-remark]: https://github.com/remarkjs/react-remark
|
828 |
|
829 | [react-syntax-highlighter]: https://github.com/react-syntax-highlighter/react-syntax-highlighter
|
830 |
|
831 | [rehype]: https://github.com/rehypejs/rehype
|
832 |
|
833 | [rehype-katex]: https://github.com/remarkjs/remark-math/tree/main/packages/rehype-katex
|
834 |
|
835 | [rehype-plugin]: https://github.com/topics/rehype-plugin
|
836 |
|
837 | [rehype-plugins]: https://github.com/rehypejs/rehype/blob/main/doc/plugins.md#list-of-plugins
|
838 |
|
839 | [rehype-react]: https://github.com/rehypejs/rehype-react
|
840 |
|
841 | [rehype-raw]: https://github.com/rehypejs/rehype-raw
|
842 |
|
843 | [rehype-sanitize]: https://github.com/rehypejs/rehype-sanitize
|
844 |
|
845 | [remark]: https://github.com/remarkjs/remark
|
846 |
|
847 | [remark-gfm]: https://github.com/remarkjs/remark-gfm
|
848 |
|
849 | [remark-math]: https://github.com/remarkjs/remark-math
|
850 |
|
851 | [remark-plugin]: https://github.com/topics/remark-plugin
|
852 |
|
853 | [remark-plugins]: https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins
|
854 |
|
855 | [remark-rehype-options]: https://github.com/remarkjs/remark-rehype#options
|
856 |
|
857 | [unified]: https://github.com/unifiedjs/unified
|
858 |
|
859 | [typescript]: https://www.typescriptlang.org
|
860 |
|
861 | [conor]: https://github.com/conorhastings
|
862 |
|
863 | [demo]: https://remarkjs.github.io/react-markdown/
|
864 |
|
865 | [section-components]: #appendix-b-components
|
866 |
|
867 | [section-plugins]: #plugins
|
868 |
|
869 | [section-security]: #security
|
870 |
|
871 | [section-syntax]: #syntax
|
872 |
|
873 | [api-allow-element]: #allowelement
|
874 |
|
875 | [api-components]: #components
|
876 |
|
877 | [api-default-url-transform]: #defaulturltransformurl
|
878 |
|
879 | [api-extra-props]: #extraprops
|
880 |
|
881 | [api-markdown]: #markdown
|
882 |
|
883 | [api-options]: #options
|
884 |
|
885 | [api-url-transform]: #urltransform
|