markdown-to-jsx 9.1.2 → 9.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,39 +25,30 @@ Some special features of the library:
25
25
  - [Entry Points](#entry-points)
26
26
  - [Main](#main)
27
27
  - [React](#react)
28
+ - [React Native](#react-native)
29
+ - [SolidJS](#solidjs)
30
+ - [Vue.js](#vuejs)
28
31
  - [HTML](#html)
29
32
  - [Markdown](#markdown)
30
33
  - [Library Options](#library-options)
31
- - [options.forceBlock](#optionsforceblock)
32
- - [options.forceInline](#optionsforceinline)
33
- - [options.wrapper](#optionswrapper)
34
- - [Other useful recipes](#other-useful-recipes)
35
- - [options.wrapperProps](#optionswrapperprops)
34
+ - [All Options](#all-options)
35
+ - [options.createElement](#optionscreateelement)
36
36
  - [options.forceWrapper](#optionsforcewrapper)
37
- - [options.overrides - Void particular banned tags](#optionsoverrides---void-particular-banned-tags)
38
- - [options.overrides - Override Any HTML Tag's Representation](#optionsoverrides---override-any-html-tags-representation)
39
- - [options.overrides - Rendering Arbitrary React Components](#optionsoverrides---rendering-arbitrary-react-components)
40
- - [options.createElement - Custom React.createElement behavior](#optionscreateelement---custom-reactcreateelement-behavior)
41
- - [options.enforceAtxHeadings](#optionsenforceatxheadings)
37
+ - [options.overrides](#optionsoverrides)
42
38
  - [options.renderRule](#optionsrenderrule)
43
39
  - [options.sanitizer](#optionssanitizer)
44
40
  - [options.slugify](#optionsslugify)
45
- - [options.disableAutoLink](#optionsdisableautolink)
46
- - [options.preserveFrontmatter](#optionspreservefrontmatter)
47
- - [options.disableParsingRawHTML](#optionsdisableparsingrawhtml)
48
- - [options.tagfilter](#optionstagfilter)
41
+ - [options.wrapper](#optionswrapper)
42
+ - [Other useful recipes](#other-useful-recipes)
43
+ - [options.wrapperProps](#optionswrapperprops)
49
44
  - [Syntax highlighting](#syntax-highlighting)
50
45
  - [Handling shortcodes](#handling-shortcodes)
51
- - [Getting the smallest possible bundle size](#getting-the-smallest-possible-bundle-size)
52
46
  - [Usage with Preact](#usage-with-preact)
53
47
  - [AST Anatomy](#ast-anatomy)
54
48
  - [Node Types](#node-types)
55
49
  - [Example AST Structure](#example-ast-structure)
56
50
  - [Type Checking](#type-checking)
57
51
  - [Gotchas](#gotchas)
58
- - [Passing props to stringified React components](#passing-props-to-stringified-react-components)
59
- - [Significant indentation inside arbitrary HTML](#significant-indentation-inside-arbitrary-html)
60
- - [Code blocks](#code-blocks)
61
52
  - [Changelog](#changelog)
62
53
  - [Donate](#donate)
63
54
 
@@ -72,37 +63,25 @@ Some special features of the library:
72
63
  - **`ast` option removed**: The `ast: true` option on `compiler()` has been removed. Use the new `parser()` function instead to access the AST directly.
73
64
 
74
65
  ```typescript
75
- // Before (v8)
76
- import { compiler } from 'markdown-to-jsx'
77
- const ast = compiler('# Hello world', { ast: true })
78
-
79
- // After (v9)
80
- import { parser } from 'markdown-to-jsx'
81
- const ast = parser('# Hello world')
66
+ /** v8 */ compiler('# Hello world', { ast: true })
67
+ /** v9 */ parser('# Hello world')
82
68
  ```
83
69
 
84
70
  - **`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.
85
71
 
86
72
  ```typescript
87
- // Before (v8)
88
- import { compiler } from 'markdown-to-jsx'
89
- compiler('≤ symbol', { namedCodesToUnicode: { le: '\u2264' } })
90
-
91
- // After (v9)
92
- import { compiler } from 'markdown-to-jsx'
93
- compiler('≤ symbol') // All entities supported automatically
73
+ /** v8 */ compiler('≤ symbol', { namedCodesToUnicode: { le: '\u2264' } })
74
+ /** v9 */ compiler('≤ symbol')
94
75
  ```
95
76
 
96
77
  - **`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.
97
78
 
98
79
  ```typescript
99
- // Before (v8) - tags rendered as JSX elements
100
- compiler('<script>alert("xss")</script>') // Rendered as <script> element
80
+ /** v8 */ tags rendered as JSX elements
81
+ /** v9 */ tags escaped by default
82
+ compiler('<script>alert("xss")</script>') // <span>&lt;script&gt;</span>
101
83
 
102
- // After (v9) - tags escaped by default
103
- compiler('<script>alert("xss")</script>') // Renders as <span>&lt;script&gt;</span>
104
-
105
- // To restore old behavior:
84
+ /** Restore old behavior */
106
85
  compiler('<script>alert("xss")</script>', { tagfilter: false })
107
86
  ```
108
87
 
@@ -128,33 +107,22 @@ import { compiler, astToMarkdown, parser } from 'markdown-to-jsx/markdown'
128
107
  1. **Replace `compiler(..., { ast: true })` with `parser()`**:
129
108
 
130
109
  ```typescript
131
- // Before
132
- import { compiler } from 'markdown-to-jsx'
133
- const ast = compiler(markdown, { ast: true })
134
-
135
- // After
136
- import { parser } from 'markdown-to-jsx'
137
- const ast = parser(markdown)
110
+ /** v8 */ compiler(markdown, { ast: true })
111
+ /** v9 */ parser(markdown)
138
112
  ```
139
113
 
140
114
  2. **Migrate React imports to `/react` entry point** (optional but recommended):
141
115
 
142
116
  ```typescript
143
- // Before
144
- import Markdown, { compiler } from 'markdown-to-jsx'
145
-
146
- // After (recommended)
147
- import Markdown, { compiler } from 'markdown-to-jsx/react'
117
+ /** Legacy */ import from 'markdown-to-jsx'
118
+ /** Recommended */ import from 'markdown-to-jsx/react'
148
119
  ```
149
120
 
150
121
  3. **Remove `namedCodesToUnicode` option**: All named HTML entities are now supported automatically, so you can remove any custom entity mappings.
151
122
 
152
123
  ```typescript
153
- // Before
154
- compiler('&le; symbol', { namedCodesToUnicode: { le: '\u2264' } })
155
-
156
- // After
157
- compiler('&le; symbol') // Works automatically
124
+ /** v8 */ compiler('&le; symbol', { namedCodesToUnicode: { le: '\u2264' } })
125
+ /** v9 */ compiler('&le; symbol')
158
126
  ```
159
127
 
160
128
  **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.
@@ -169,21 +137,15 @@ compiler('&le; symbol') // Works automatically
169
137
  - Type `ParserResult` renamed to `ASTNode` - If you were using `MarkdownToJSX.ParserResult` in your code, update to `MarkdownToJSX.ASTNode`
170
138
 
171
139
  ```typescript
172
- // Before
173
- const nodes: MarkdownToJSX.ParserResult[] = parse(markdown)
174
-
175
- // After
176
- const nodes: MarkdownToJSX.ASTNode[] = parse(markdown)
140
+ /** v7 */ MarkdownToJSX.ParserResult[]
141
+ /** v8+ */ MarkdownToJSX.ASTNode[]
177
142
  ```
178
143
 
179
144
  - 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:
180
145
 
181
146
  ```typescript
182
- // Before
183
- if (node.type === RuleType.textBolded) { ... }
184
-
185
- // After
186
- if (node.type === RuleType.textFormatted && node.bold) { ... }
147
+ /** v7 */ RuleType.textBolded
148
+ /** v8+ */ RuleType.textFormatted && node.bold
187
149
  ```
188
150
 
189
151
  </details>
@@ -239,290 +201,224 @@ For React-specific usage, import from the `/react` entry point:
239
201
  ```tsx
240
202
  import Markdown, { compiler, parser, astToJSX } from 'markdown-to-jsx/react'
241
203
 
242
- // Use compiler for markdown → JSX
243
204
  const jsxElement = compiler('# Hello world')
244
205
 
245
- const markdown = `# Hello world`
246
-
247
206
  function App() {
248
- return <Markdown children={markdown} />
207
+ return <Markdown children="# Hello world" />
249
208
  }
250
209
 
251
- // Or use parser + astToJSX for total control
210
+ /** Or use parser + astToJSX */
252
211
  const ast = parser('# Hello world')
253
212
  const jsxElement2 = astToJSX(ast)
254
213
  ```
255
214
 
256
- #### HTML
215
+ #### React Native
257
216
 
258
- For HTML string output (server-side rendering), import from the `/html` entry point:
217
+ For React Native usage, import from the `/native` entry point:
259
218
 
260
219
  ```tsx
261
- import { compiler, html, parser } from 'markdown-to-jsx/html'
262
-
263
- // Convenience function that combines parsing and HTML rendering
264
- const htmlString = compiler('# Hello world')
265
- // Returns: '<h1>Hello world</h1>'
266
-
267
- // Or use parser + html separately for more control
268
- const ast = parser('# Hello world')
269
- const htmlString2 = html(ast)
270
- ```
271
-
272
- #### Markdown
273
-
274
- For markdown-to-markdown compilation (normalization and formatting), import from the `/markdown` entry point:
275
-
276
- ```typescript
277
- import { compiler, astToMarkdown, parser } from 'markdown-to-jsx/markdown'
278
-
279
- // Convenience function that parses and recompiles markdown
280
- const normalizedMarkdown = compiler('# Hello world\n\nExtra spaces!')
281
- // Returns: '# Hello world\n\nExtra spaces!\n'
282
-
283
- // Or work with AST directly
284
- const ast = parser('# Hello world')
285
- const normalizedMarkdown2 = astToMarkdown(ast)
286
- // Returns: '# Hello world\n'
287
- ```
288
-
289
- ### Library Options
290
-
291
- #### options.forceBlock
292
-
293
- 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":
220
+ import Markdown, { compiler, parser, astToNative } from 'markdown-to-jsx/native'
221
+ import { View, Text, StyleSheet, Linking } from 'react-native'
222
+
223
+ const nativeElement = compiler('# Hello world', {
224
+ styles: {
225
+ heading1: { fontSize: 32, fontWeight: 'bold' },
226
+ paragraph: { marginVertical: 8 },
227
+ link: { color: 'blue', textDecorationLine: 'underline' },
228
+ },
229
+ onLinkPress: url => {
230
+ Linking.openURL(url)
231
+ },
232
+ })
294
233
 
295
- ```md
296
- Hello. _Beautiful_ day isn't it?
297
- ```
234
+ const markdown = `# Hello world
298
235
 
299
- But this string would be considered "block" due to the existence of a header tag, which is a block-level HTML element:
236
+ This is a [link](https://example.com) with **bold** and *italic* text.
237
+ `
300
238
 
301
- ```md
302
- # Whaddup?
239
+ function App() {
240
+ return (
241
+ <View>
242
+ <Markdown
243
+ children={markdown}
244
+ options={{
245
+ styles: StyleSheet.create({
246
+ heading1: { fontSize: 32, fontWeight: 'bold' },
247
+ paragraph: { marginVertical: 8 },
248
+ link: { color: 'blue', textDecorationLine: 'underline' },
249
+ }),
250
+ onLinkPress: url => {
251
+ Linking.openURL(url)
252
+ },
253
+ }}
254
+ />
255
+ </View>
256
+ )
257
+ }
303
258
  ```
304
259
 
305
- However, if you really want all input strings to be treated as "block" layout, simply pass `options.forceBlock = true` like this:
260
+ **React Native-specific options:**
306
261
 
307
- ```tsx
308
- <Markdown options={{ forceBlock: true }}>Hello there old chap!</Markdown>
262
+ - `onLinkPress?: (url: string, title?: string) => void` - Custom handler for link presses (defaults to `Linking.openURL`)
263
+ - `onLinkLongPress?: (url: string, title?: string) => void` - Handler for link long presses
264
+ - `styles?: Partial<Record<NativeStyleKey, StyleProp<ViewStyle | TextStyle | ImageStyle>>>` - Style overrides for each element type
265
+ - `wrapperProps?: ViewProps | TextProps` - Props for the wrapper component (defaults to `View` for block, `Text` for inline)
309
266
 
310
- // or
267
+ **HTML Tag Mapping:**
268
+ HTML tags are automatically mapped to React Native components:
311
269
 
312
- compiler('Hello there old chap!', { forceBlock: true })
270
+ - `<img>` `Image` component
271
+ - Block elements (`<div>`, `<section>`, `<article>`, `<blockquote>`, `<ul>`, `<ol>`, `<li>`, `<table>`, etc.) → `View` component
272
+ - Inline elements (`<span>`, `<strong>`, `<em>`, `<a>`, etc.) → `Text` component
273
+ - Type 1 blocks (`<pre>`, `<script>`, `<style>`, `<textarea>`) → `View` component
313
274
 
314
- // renders
315
- <p>Hello there old chap!</p>
316
- ```
275
+ **Note:** Links are underlined by default for better accessibility and discoverability. You can override this via the `styles.link` option.
317
276
 
318
- #### options.forceInline
277
+ #### SolidJS
319
278
 
320
- The inverse is also available by passing `options.forceInline = true`:
279
+ For SolidJS usage, import from the `/solid` entry point:
321
280
 
322
281
  ```tsx
323
- <Markdown options={{ forceInline: true }}># You got it babe!</Markdown>
324
-
325
- // or
326
- compiler('# You got it babe!', { forceInline: true })
282
+ import Markdown, {
283
+ compiler,
284
+ parser,
285
+ astToJSX,
286
+ MarkdownProvider,
287
+ } from 'markdown-to-jsx/solid'
288
+ import { createSignal } from 'solid-js'
327
289
 
328
- // renders
329
- <span># You got it babe!</span>
330
- ```
290
+ // Static content
291
+ const solidElement = compiler('# Hello world')
331
292
 
332
- #### options.wrapper
333
-
334
- 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.
335
-
336
- ```tsx
337
- const str = '# Heck Yes\n\nThis is great!'
338
-
339
- <Markdown options={{ wrapper: 'article' }}>
340
- {str}
341
- </Markdown>;
342
-
343
- // or
293
+ function App() {
294
+ return <Markdown children="# Hello world" />
295
+ }
344
296
 
345
- compiler(str, { wrapper: 'article' });
297
+ // Reactive content (automatically updates when content changes)
298
+ function ReactiveApp() {
299
+ const [content, setContent] = createSignal('# Hello world')
300
+ return <Markdown>{content}</Markdown>
301
+ }
346
302
 
347
- // renders
303
+ // Or use parser + astToJSX
304
+ const ast = parser('# Hello world')
305
+ const solidElement2 = astToJSX(ast)
348
306
 
349
- <article>
350
- <h1>Heck Yes</h1>
351
- <p>This is great!</p>
352
- </article>
307
+ // Use context for default options
308
+ function AppWithContext() {
309
+ return (
310
+ <MarkdownProvider options={{ sanitizer: customSanitizer }}>
311
+ <Markdown># Content</Markdown>
312
+ </MarkdownProvider>
313
+ )
314
+ }
353
315
  ```
354
316
 
355
- ##### Other useful recipes
356
-
357
- To get an array of children back without a wrapper, set `wrapper` to `null`. This is particularly useful when using `compiler(…)` directly.
358
-
359
- ```tsx
360
- compiler('One\n\nTwo\n\nThree', { wrapper: null })
361
-
362
- // returns
363
- ;[<p>One</p>, <p>Two</p>, <p>Three</p>]
364
- ```
317
+ **SolidJS-specific features:**
365
318
 
366
- 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.
319
+ - **Reactive content**: The `Markdown` component accepts signals/accessors for automatic updates when markdown content changes
320
+ - **Memoization**: AST parsing is automatically memoized for optimal performance
321
+ - **Context API**: Use `MarkdownProvider` to provide default options and avoid prop drilling
367
322
 
368
- #### options.wrapperProps
323
+ #### Vue.js
369
324
 
370
- Props to apply to the wrapper element when `wrapper` is used.
325
+ For Vue.js 3 usage, import from the `/vue` entry point:
371
326
 
372
327
  ```tsx
373
- <Markdown options={{
374
- wrapper: 'article',
375
- wrapperProps: { className: 'post', 'data-testid': 'markdown-content' }
376
- }}>
377
- # Hello World
378
- </Markdown>
328
+ import Markdown, { compiler, parser, astToJSX } from 'markdown-to-jsx/vue'
329
+ import { h } from 'vue'
379
330
 
380
- // renders
381
- <article class="post" data-testid="markdown-content">
382
- <h1>Hello World</h1>
383
- </article>
384
- ```
331
+ // Using compiler
332
+ const vnode = compiler('# Hello world')
385
333
 
386
- #### options.forceWrapper
387
-
388
- 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`.
334
+ // Using component
335
+ <Markdown children="# Hello world" />
389
336
 
390
- ```tsx
391
- // Using `forceWrapper` with a single, inline child…
392
- <Markdown options={{ wrapper: 'aside', forceWrapper: true }}>
393
- Mumble, mumble…
394
- </Markdown>
395
-
396
- // renders
397
-
398
- <aside>Mumble, mumble…</aside>
337
+ // Or use parser + astToJSX
338
+ const ast = parser('# Hello world')
339
+ const vnode2 = astToJSX(ast)
399
340
  ```
400
341
 
401
- #### options.overrides - Void particular banned tags
342
+ **Vue.js-specific features:**
402
343
 
403
- 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.
344
+ - **Vue 3 support**: Uses Vue 3's `h()` render function API
345
+ - **JSX support**: Works with Vue 3 JSX via `@vue/babel-plugin-jsx` or `@vitejs/plugin-vue-jsx`
346
+ - **HTML attributes**: Uses standard HTML attributes (`class` instead of `className`)
347
+ - **Component overrides**: Support for both Options API and Composition API componen
404
348
 
405
- **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:
406
-
407
- - Remove additional tags not covered by `tagfilter` (like `object`)
408
- - Have more control over tag removal vs. escaping
409
- - Disable `tagfilter` but still want to remove specific tags
349
+ #### HTML
410
350
 
411
- For example, to void the `iframe` tag:
351
+ For HTML string output (server-side rendering), import from the `/html` entry point:
412
352
 
413
353
  ```tsx
414
- import Markdown from 'markdown-to-jsx'
415
- import React from 'react'
416
- import { render } from 'react-dom'
354
+ import { compiler, html, parser } from 'markdown-to-jsx/html'
417
355
 
418
- render(
419
- <Markdown options={{ overrides: { iframe: () => null } }}>
420
- <iframe src="https://potentially-malicious-web-page.com/"></iframe>
421
- </Markdown>,
422
- document.body
423
- )
356
+ const htmlString = compiler('# Hello world')
424
357
 
425
- // renders: ""
358
+ /** Or use parser + html */
359
+ const ast = parser('# Hello world')
360
+ const htmlString2 = html(ast)
426
361
  ```
427
362
 
428
- 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.
429
-
430
- #### options.overrides - Override Any HTML Tag's Representation
431
-
432
- 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.
433
-
434
- ```tsx
435
- import Markdown from 'markdown-to-jsx'
436
- import React from 'react'
437
- import { render } from 'react-dom'
438
-
439
- // surprise, it's a div instead!
440
- const MyParagraph = ({ children, ...props }) => <div {...props}>{children}</div>
441
-
442
- render(
443
- <Markdown
444
- options={{
445
- overrides: {
446
- h1: {
447
- component: MyParagraph,
448
- props: {
449
- className: 'foo',
450
- },
451
- },
452
- },
453
- }}
454
- >
455
- # Hello world!
456
- </Markdown>,
457
- document.body
458
- )
363
+ #### Markdown
459
364
 
460
- /*
461
- renders:
365
+ For markdown-to-markdown compilation (normalization and formatting), import from the `/markdown` entry point:
462
366
 
463
- <div class="foo">
464
- Hello World
465
- </div>
466
- */
467
- ```
367
+ ```typescript
368
+ import { compiler, astToMarkdown, parser } from 'markdown-to-jsx/markdown'
468
369
 
469
- If you only wish to provide a component override, a simplified syntax is available:
370
+ const normalizedMarkdown = compiler('# Hello world\n\nExtra spaces!')
470
371
 
471
- ```js
472
- {
473
- overrides: {
474
- h1: MyParagraph,
475
- },
476
- }
372
+ /** Or work with AST */
373
+ const ast = parser('# Hello world')
374
+ const normalizedMarkdown2 = astToMarkdown(ast)
477
375
  ```
478
376
 
479
- Depending on the type of element, there are some props that must be preserved to ensure the markdown is converted as intended. They are:
480
-
481
- - `a`: `title`, `href`
482
- - `img`: `title`, `alt`, `src`
483
- - `input[type="checkbox"]`: `checked`, `readonly` (specifically, the one rendered by a GFM task list)
484
- - `ol`: `start`
485
- - `td`: `style`
486
- - `th`: `style`
487
-
488
- Any conflicts between passed `props` and the specific properties above will be resolved in favor of `markdown-to-jsx`'s code.
489
-
490
- Some element mappings are a bit different from other libraries, in particular:
491
-
492
- - `span`: Used for inline text.
493
- - `code`: Used for inline code.
494
- - `pre > code`: Code blocks are a `code` element with a `pre` as its direct ancestor.
495
-
496
- #### options.overrides - Rendering Arbitrary React Components
377
+ ### Library Options
497
378
 
498
- 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.
379
+ #### All Options
380
+
381
+ | Option | Type | Default | Description |
382
+ | ----------------------- | ----------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
383
+ | `createElement` | `function` | - | Custom createElement behavior (React/React Native/SolidJS/Vue only). See [createElement](#optionscreateelement) for details. |
384
+ | `disableAutoLink` | `boolean` | `false` | Disable automatic conversion of bare URLs to anchor tags. |
385
+ | `disableParsingRawHTML` | `boolean` | `false` | Disable parsing of raw HTML into JSX. |
386
+ | `enforceAtxHeadings` | `boolean` | `false` | Require space between `#` and header text (GFM spec compliance). |
387
+ | `forceBlock` | `boolean` | `false` | Force all content to be treated as block-level. |
388
+ | `forceInline` | `boolean` | `false` | Force all content to be treated as inline. |
389
+ | `forceWrapper` | `boolean` | `false` | Force wrapper even with single child (React/React Native/Vue only). See [forceWrapper](#optionsforcewrapper) for details. |
390
+ | `overrides` | `object` | - | Override HTML tag rendering. See [overrides](#optionsoverrides) for details. |
391
+ | `preserveFrontmatter` | `boolean` | `false` | Include frontmatter in rendered output (as `<pre>` for HTML/JSX, included in markdown). Behavior varies by compiler type. |
392
+ | `renderRule` | `function` | - | Custom rendering for AST rules. See [renderRule](#optionsrenderrule) for details. |
393
+ | `sanitizer` | `function` | built-in | Custom URL sanitizer function. See [sanitizer](#optionssanitizer) for details. |
394
+ | `slugify` | `function` | built-in | Custom slug generation for heading IDs. See [slugify](#optionsslugify) for details. |
395
+ | `tagfilter` | `boolean` | `true` | Escape dangerous HTML tags (`script`, `iframe`, `style`, etc.) to prevent XSS. |
396
+ | `wrapper` | `string \| component \| null` | `'div'` | Wrapper element for multiple children (React/React Native/Vue only). See [wrapper](#optionswrapper) for details. |
397
+ | `wrapperProps` | `object` | - | Props for wrapper element (React/React Native/Vue only). See [wrapperProps](#optionswrapperprops) for details. |
398
+
399
+ #### options.createElement
499
400
 
500
- 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:
401
+ 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]`](https://reactjs.org/docs/react-api.html#createelement):
501
402
 
502
- ```tsx
403
+ ```javascript
503
404
  import Markdown from 'markdown-to-jsx'
504
405
  import React from 'react'
505
406
  import { render } from 'react-dom'
506
407
 
507
- import DatePicker from './date-picker'
508
-
509
408
  const md = `
510
- # DatePicker
511
-
512
- The DatePicker works by supplying a date to bias towards,
513
- as well as a default timezone.
514
-
515
- <DatePicker biasTowardDateTime="2017-12-05T07:39:36.091Z" timezone="UTC+5" />
409
+ # Hello world
516
410
  `
517
411
 
518
412
  render(
519
413
  <Markdown
520
414
  children={md}
521
415
  options={{
522
- overrides: {
523
- DatePicker: {
524
- component: DatePicker,
525
- },
416
+ createElement(type, props, children) {
417
+ return (
418
+ <div className="parent">
419
+ {React.createElement(type, props, children)}
420
+ </div>
421
+ )
526
422
  },
527
423
  }}
528
424
  />,
@@ -530,129 +426,61 @@ render(
530
426
  )
531
427
  ```
532
428
 
533
- `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.
429
+ #### options.forceWrapper
534
430
 
535
- In the following case, `DatePicker` could simply run `parseInt()` on the passed `startTime` for example:
431
+ 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`.
536
432
 
537
433
  ```tsx
538
- import Markdown from 'markdown-to-jsx'
539
- import React from 'react'
540
- import { render } from 'react-dom'
541
-
542
- import DatePicker from './date-picker'
543
-
544
- const md = `
545
- # DatePicker
546
-
547
- The DatePicker works by supplying a date to bias towards,
548
- as well as a default timezone.
434
+ // Using `forceWrapper` with a single, inline child…
435
+ <Markdown options={{ wrapper: 'aside', forceWrapper: true }}>
436
+ Mumble, mumble…
437
+ </Markdown>
549
438
 
550
- <DatePicker
551
- biasTowardDateTime="2017-12-05T07:39:36.091Z"
552
- timezone="UTC+5"
553
- startTime={1514579720511}
554
- />
555
- `
439
+ // renders
556
440
 
557
- render(
558
- <Markdown
559
- children={md}
560
- options={{
561
- overrides: {
562
- DatePicker: {
563
- component: DatePicker,
564
- },
565
- },
566
- }}
567
- />,
568
- document.body
569
- )
441
+ <aside>Mumble, mumble…</aside>
570
442
  ```
571
443
 
572
- Another possibility is to use something like [recompose's `withProps()` HOC](https://github.com/acdlite/recompose/blob/main/docs/API.md#withprops) to create various pregenerated scenarios and then reference them by name in the markdown:
573
-
574
- ```tsx
575
- import Markdown from 'markdown-to-jsx'
576
- import React from 'react'
577
- import { render } from 'react-dom'
578
- import withProps from 'recompose/withProps'
579
-
580
- import DatePicker from './date-picker'
444
+ #### options.overrides
581
445
 
582
- const DecemberDatePicker = withProps({
583
- range: {
584
- start: new Date('2017-12-01'),
585
- end: new Date('2017-12-31'),
586
- },
587
- timezone: 'UTC+5',
588
- })(DatePicker)
446
+ Override HTML tag rendering or render custom React components. Three use cases:
589
447
 
590
- const md = `
591
- # DatePicker
448
+ **1. Remove tags:** Return `null` to completely remove tags (beyond `tagfilter` escaping):
592
449
 
593
- The DatePicker works by supplying a date to bias towards,
594
- as well as a default timezone.
450
+ ```tsx
451
+ <Markdown options={{ overrides: { iframe: () => null } }}>
452
+ <iframe src="..."></iframe>
453
+ </Markdown>
454
+ ```
595
455
 
596
- <DatePicker
597
- biasTowardDateTime="2017-12-05T07:39:36.091Z"
598
- timezone="UTC+5"
599
- startTime={1514579720511}
600
- />
456
+ **2. Override HTML tags:** Change component, props, or both:
601
457
 
602
- Here's an example of a DatePicker pre-set to only the month of December:
458
+ ```tsx
459
+ const MyParagraph = ({ children, ...props }) => <div {...props}>{children}</div>
603
460
 
604
- <DecemberDatePicker />
605
- `
461
+ <Markdown options={{ overrides: { h1: { component: MyParagraph, props: { className: 'foo' } } } }}>
462
+ # Hello
463
+ </Markdown>
606
464
 
607
- render(
608
- <Markdown
609
- children={md}
610
- options={{
611
- overrides: {
612
- DatePicker,
613
- DecemberDatePicker,
614
- },
615
- }}
616
- />,
617
- document.body
618
- )
465
+ /** Simplified */ { overrides: { h1: MyParagraph } }
619
466
  ```
620
467
 
621
- #### options.createElement - Custom React.createElement behavior
622
-
623
- 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]`](https://reactjs.org/docs/react-api.html#createelement):
468
+ **3. Render React components:** Use custom components in markdown:
624
469
 
625
- ```javascript
626
- import Markdown from 'markdown-to-jsx'
627
- import React from 'react'
628
- import { render } from 'react-dom'
470
+ ```tsx
471
+ import DatePicker from './date-picker'
629
472
 
630
- const md = `
631
- # Hello world
632
- `
473
+ const md = `<DatePicker timezone="UTC+5" startTime={1514579720511} />`
633
474
 
634
- render(
635
- <Markdown
636
- children={md}
637
- options={{
638
- createElement(type, props, children) {
639
- return (
640
- <div className="parent">
641
- {React.createElement(type, props, children)}
642
- </div>
643
- )
644
- },
645
- }}
646
- />,
647
- document.body
648
- )
475
+ <Markdown options={{ overrides: { DatePicker } }}>{md}</Markdown>
649
476
  ```
650
477
 
651
- #### options.enforceAtxHeadings
652
-
653
- Forces the compiler to have space between hash sign `#` and the header text which is explicitly stated in the most of the [markdown specs](https://github.github.com/gfm/#atx-heading).
478
+ **Important notes:**
654
479
 
655
- > The opening sequence of `#` characters must be followed by a space or by the end of line.
480
+ - Props are passed as strings; parse them in your component (e.g., `JSON.parse(columns)`)
481
+ - JSX interpolations (`{value}`) are passed as raw strings
482
+ - Some props are preserved: `a` (`href`, `title`), `img` (`src`, `alt`, `title`), `input[type="checkbox"]` (`checked`, `readonly`), `ol` (`start`), `td`/`th` (`style`)
483
+ - Element mappings: `span` for inline text, `code` for inline code, `pre > code` for code blocks
656
484
 
657
485
  #### options.renderRule
658
486
 
@@ -702,7 +530,7 @@ This can be overridden and replaced with a custom sanitizer if desired via `opti
702
530
  // or
703
531
 
704
532
  compiler('[foo](javascript:alert("foo"))', {
705
- sanitizer: (value, tag, attribute) => value,
533
+ sanitizer: value => value,
706
534
  })
707
535
  ```
708
536
 
@@ -711,135 +539,51 @@ compiler('[foo](javascript:alert("foo"))', {
711
539
  By default, a [lightweight deburring function](https://github.com/quantizor/markdown-to-jsx/blob/bc2f57412332dc670f066320c0f38d0252e0f057/index.js#L261-L275) 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:
712
540
 
713
541
  ```tsx
714
- <Markdown options={{ slugify: str => str }}># 中文</Markdown>
715
-
716
- // or
717
-
542
+ ;<Markdown options={{ slugify: str => str }}># 中文</Markdown>
718
543
  compiler('# 中文', { slugify: str => str })
719
-
720
- // renders:
721
- <h1 id="中文">中文</h1>
722
544
  ```
723
545
 
724
546
  The original function is available as a library export called `slugify`.
725
547
 
726
- #### options.disableAutoLink
548
+ #### options.wrapper
727
549
 
728
- By default, bare URLs in the markdown document will be converted into an anchor tag. This behavior can be disabled if desired.
550
+ 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.
729
551
 
730
552
  ```tsx
731
- <Markdown options={{ disableAutoLink: true }}>
732
- The URL https://quantizor.dev will not be rendered as an anchor tag.
733
- </Markdown>
734
-
735
- // or
736
-
737
- compiler(
738
- 'The URL https://quantizor.dev will not be rendered as an anchor tag.',
739
- { disableAutoLink: true }
740
- )
553
+ const str = '# Heck Yes\n\nThis is great!'
741
554
 
742
- // renders:
555
+ <Markdown options={{ wrapper: 'article' }}>{str}</Markdown>
743
556
 
744
- <span>
745
- The URL https://quantizor.dev will not be rendered as an anchor tag.
746
- </span>
557
+ compiler(str, { wrapper: 'article' })
747
558
  ```
748
559
 
749
- #### options.preserveFrontmatter
750
-
751
- 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.
752
-
753
- | Compiler Type | Default Behavior | When `preserveFrontmatter: true` | When `preserveFrontmatter: false` |
754
- | ------------------------ | --------------------------- | -------------------------------- | --------------------------------- |
755
- | **React/HTML** | ❌ Don't render frontmatter | ✅ Render as `<pre>` element | ❌ Don't render frontmatter |
756
- | **Markdown-to-Markdown** | ✅ Preserve frontmatter | ✅ Preserve frontmatter | ❌ Exclude frontmatter |
757
-
758
- ```tsx
759
- <Markdown options={{ preserveFrontmatter: true }}>
760
- {`---
761
- title: My Document
762
- author: John Doe
763
- ---
764
-
765
- # Content
766
-
767
- This is the main content.`
768
- }
769
- </Markdown>
770
-
771
- // renders:
772
-
773
- <div>
774
- <pre>---
775
- title: My Document
776
- author: John Doe
777
- ---</pre>
778
- <h1>Content</h1>
779
- <p>This is the main content.</p>
780
- </div>
781
- ```
560
+ ##### Other useful recipes
782
561
 
783
- For markdown-to-markdown compilation:
562
+ To get an array of children back without a wrapper, set `wrapper` to `null`. This is particularly useful when using `compiler(…)` directly.
784
563
 
785
564
  ```tsx
786
- import { compiler } from 'markdown-to-jsx/markdown'
787
-
788
- const markdown = `---
789
- title: My Document
790
- author: John Doe
791
- ---
792
-
793
- # Content`
794
-
795
- // With preserveFrontmatter: true (default)
796
- compiler(markdown, { preserveFrontmatter: true })
797
- // returns: "---\ntitle: My Document\nauthor: John Doe\n---\n\n# Content"
798
-
799
- // With preserveFrontmatter: false
800
- compiler(markdown, { preserveFrontmatter: false })
801
- // returns: "# Content"
565
+ compiler('One\n\nTwo\n\nThree', { wrapper: null })[
566
+ /** Returns */ ((<p>One</p>), (<p>Two</p>), (<p>Three</p>))
567
+ ]
802
568
  ```
803
569
 
804
- #### options.disableParsingRawHTML
805
-
806
- By default, raw HTML is parsed to JSX. This behavior can be disabled if desired.
807
-
808
- ```tsx
809
- <Markdown options={{ disableParsingRawHTML: true }}>
810
- This text has <span>html</span> in it but it won't be rendered
811
- </Markdown>;
812
-
813
- // or
814
-
815
- compiler('This text has <span>html</span> in it but it won't be rendered', { disableParsingRawHTML: true });
816
-
817
- // renders:
818
-
819
- <span>This text has &lt;span&gt;html&lt;/span&gt; in it but it won't be rendered</span>
820
- ```
570
+ 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.
821
571
 
822
- #### options.tagfilter
572
+ #### options.wrapperProps
823
573
 
824
- 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`.
574
+ Props to apply to the wrapper element when `wrapper` is used.
825
575
 
826
576
  ```tsx
827
- // Tags are escaped by default (GFM-compliant)
828
- compiler('<script>alert("xss")</script>')
829
- // HTML output: '<span>&lt;script&gt;</span>'
830
- // React output: <span>&lt;script&gt;</span>
831
-
832
- // Disable tag filtering:
833
- compiler('<script>alert("xss")</script>', { tagfilter: false })
834
- // HTML output: '<script></script>'
835
- // React output: <script></script>
577
+ <Markdown
578
+ options={{
579
+ wrapper: 'article',
580
+ wrapperProps: { className: 'post', 'data-testid': 'markdown-content' },
581
+ }}
582
+ >
583
+ # Hello World
584
+ </Markdown>
836
585
  ```
837
586
 
838
- **Note**: Even when `tagfilter` is disabled, other security measures remain active:
839
-
840
- - URL sanitization preventing `javascript:` and `vbscript:` schemes in `href` and `src` attributes
841
- - Protection against `data:` URLs (except safe `data:image/*` MIME types)
842
-
843
587
  ### Syntax highlighting
844
588
 
845
589
  When using [fenced code blocks](https://www.markdownguide.org/extended-syntax/#syntax-highlighting) 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`:
@@ -876,7 +620,7 @@ function App() {
876
620
  }
877
621
 
878
622
  function SyntaxHighlightedCode(props) {
879
- const ref = (React.useRef < HTMLElement) | (null > null)
623
+ const ref = React.useRef<HTMLElement | null>(null)
880
624
 
881
625
  React.useEffect(() => {
882
626
  if (ref.current && props.className?.includes('lang-') && window.hljs) {
@@ -933,24 +677,10 @@ function Example() {
933
677
  </Markdown>
934
678
  )
935
679
  }
936
-
937
- // renders
938
- // <span>On a beautiful summer day, all I want to do is <span>🙂</span>.</span>
939
680
  ```
940
681
 
941
682
  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!
942
683
 
943
- ### Getting the smallest possible bundle size
944
-
945
- 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.
946
-
947
- Here are instructions for some of the popular bundlers:
948
-
949
- - [webpack](https://webpack.js.org/guides/production/#specify-the-environment)
950
- - [browserify plugin](https://github.com/hughsk/envify)
951
- - [parcel](https://parceljs.org/production.html)
952
- - [fuse-box](http://fuse-box.org/plugins/replace-plugin#notes)
953
-
954
684
  ### Usage with Preact
955
685
 
956
686
  Everything will work just fine! Simply [Alias `react` to `preact/compat`](https://preactjs.com/guide/v10/switching-to-preact#setting-up-compat) like you probably already are doing.
@@ -1122,88 +852,24 @@ if (node.type === RuleType.heading) {
1122
852
 
1123
853
  ### Gotchas
1124
854
 
1125
- #### Passing props to stringified React components
1126
-
1127
- Using the [`options.overrides`](#optionsoverrides---rendering-arbitrary-react-components) 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.
855
+ **Props are stringified:** When using `overrides` with React components, props are passed as strings. Parse them in your component:
1128
856
 
1129
857
  ```tsx
1130
- const Table: React.FC<
1131
- JSX.IntrinsicElements['table'] & {
1132
- columns: string
1133
- dataSource: string
1134
- }
1135
- > = ({ columns, dataSource, ...props }) => {
858
+ const Table = ({ columns, dataSource, ...props }) => {
1136
859
  const parsedColumns = JSON.parse(columns)
1137
860
  const parsedData = JSON.parse(dataSource)
1138
-
1139
- return (
1140
- <div {...props}>
1141
- <h1>Columns</h1>
1142
- {parsedColumns.map(column => (
1143
- <span key={column.key}>{column.title}</span>
1144
- ))}
1145
-
1146
- <h2>Data</h2>
1147
- {parsedData.map(datum => (
1148
- <span key={datum.key}>{datum.Month}</span>
1149
- ))}
1150
- </div>
1151
- )
861
+ // ... use parsed data
1152
862
  }
1153
-
1154
- /**
1155
- * Example HTML in markdown:
1156
- *
1157
- * <Table
1158
- * columns={[{ title: 'Month', dataIndex: 'Month', key: 'Month' }]}
1159
- * dataSource={[
1160
- * {
1161
- * Month: '2024-09-01',
1162
- * 'Forecasted Revenue': '$3,137,678.85',
1163
- * 'Forecasted Expenses': '$2,036,660.28',
1164
- * key: 0,
1165
- * },
1166
- * ]}
1167
- * />
1168
- */
1169
- ```
1170
-
1171
- #### Significant indentation inside arbitrary HTML
1172
-
1173
- People usually write HTML like this:
1174
-
1175
- ```html
1176
- <div>Hey, how are you?</div>
1177
- ```
1178
-
1179
- 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.
1180
-
1181
- To get around this, `markdown-to-jsx` left-trims approximately as much whitespace as the first line inside the HTML block. So for example:
1182
-
1183
- ```html
1184
- <div># Hello How are you?</div>
1185
863
  ```
1186
864
 
1187
- 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.
1188
-
1189
- > 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. 🙃
1190
-
1191
- #### Code blocks
1192
-
1193
- ⛔️
1194
-
1195
- ```md
1196
- <div>
1197
- var some = code();
1198
- </div>
1199
- ```
865
+ **HTML indentation:** Leading whitespace in HTML blocks is auto-trimmed based on the first line's indentation to avoid markdown syntax conflicts.
1200
866
 
1201
-
867
+ **Code in HTML:** Don't put code directly in HTML divs. Use fenced code blocks instead:
1202
868
 
1203
869
  ````md
1204
870
  <div>
1205
871
  ```js
1206
- var some = code();
872
+ var code = here();
1207
873
  ```
1208
874
  </div>
1209
875
  ````