
Syncfusion React Markdown Converter
- 384 installs
- 3 repo stars
- Updated July 28, 2026
- syncfusion/react-ui-components-skills
Use syncfusion-react-markdown-converter for development tasks
About
syncfusion-react-markdown-converter: A skill for development. This provides functionality for development workflows.
- syncfusion-react-markdown-converter
Syncfusion React Markdown Converter by the numbers
- 384 all-time installs (skills.sh)
- +52 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #1,130 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/syncfusion/react-ui-components-skills --skill syncfusion-react-markdown-converterAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 384 |
|---|---|
| repo stars | ★ 3 |
| Last updated | July 28, 2026 |
| Repository | syncfusion/react-ui-components-skills ↗ |
What it does
Use syncfusion-react-markdown-converter for development tasks
Files
Implementing Syncfusion React Markdown Converter
The Syncfusion Markdown Converter is a lightweight utility that transforms Markdown-formatted text into clean, semantic HTML. It is typically used alongside the Syncfusion React Rich Text Editor (RichTextEditorComponent) in Markdown editing mode to provide a live preview of rendered content.
When to Use This Skill
- Convert Markdown text to HTML in a React application
- Display a live preview of Markdown content as the user types
- Configure conversion behavior (GFM support, line breaks, async mode, error suppression)
- Build a Markdown editor with HTML preview using
RichTextEditorComponentin Markdown mode - Integrate
MarkdownConverter.toHtml()with the Syncfusion React Rich Text Editor
Documentation and Navigation Guide
Getting Started
- React setup and project creation (Vite / CRA)
- Installing
@syncfusion/ej2-markdown-converterand@syncfusion/ej2-react-richtexteditor - CSS imports for the material3 theme
- Module injection (
MarkdownEditor,Toolbar,Image,Link,Table) - Running the application
Convert Markdown to HTML — toHtml API
MarkdownConverter.toHtml()method signature- Basic usage example (standalone, no editor required)
- Supported Markdown elements (headings, lists, tables, links, images, inline styles)
- Using the return value with
dangerouslySetInnerHTML
Configurable Options
MarkdownConverterOptionsinterfaceasync— asynchronous conversion for large contentgfm— GitHub Flavored Markdown support (default: true)lineBreak— convert single line breaks to<br>(default: false)silence— suppress errors on invalid Markdown (default: false)- Full example passing options to
toHtml()
Rich Text Editor Integration
- Markdown mode with
editorMode="Markdown" - Live preview using
created,change, andactionCompleteevents - Toolbar configuration for Markdown editing
MarkdownFormatterfor custom Markdown syntax- Custom preview toggle button (full preview pattern)
- Full working example with preview toggle
Quick Start
Install packages and wire up the converter in three steps:
1. Install packages:
npm install @syncfusion/ej2-markdown-converter
npm install @syncfusion/ej2-react-richtexteditor2. Import CSS in `src/styles.css`:
@import '../node_modules/@syncfusion/ej2-base/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-richtexteditor/styles/material3.css';3. Convert Markdown to HTML:
import { MarkdownConverter } from '@syncfusion/ej2-markdown-converter';
const markdown = '# Hello World\nThis is **Markdown** text.';
const html = MarkdownConverter.toHtml(markdown);
console.log(html);
// Output: <h1>Hello World</h1><p>This is <strong>Markdown</strong> text.</p>Common Patterns
Pattern 1: Standalone Conversion (No Editor)
When you only need to convert a Markdown string to HTML without an editor UI:
import { MarkdownConverter } from '@syncfusion/ej2-markdown-converter';
const html = MarkdownConverter.toHtml(markdownString);
document.getElementById('preview')!.innerHTML = html as string;Pattern 2: Live Preview on Keyup
Convert on every keystroke inside a Rich Text Editor textarea:
const markdownConversion = () => {
if (mdsource?.classList.contains('e-active')) {
const htmlPreview = rteObj.element.querySelector('#' + rteObj.getID() + 'html-view') as HTMLElement;
htmlPreview.innerHTML = MarkdownConverter.toHtml(
(rteObj.contentModule.getEditPanel() as HTMLTextAreaElement).value
) as string;
}
};Pattern 3: Conversion with Options
Disable GFM and enable line-break conversion:
const html = MarkdownConverter.toHtml(markdownString, {
gfm: false,
lineBreak: true
});Pattern 4: Side-by-Side Preview with Splitter
For a full editor + preview experience, use the Syncfusion Splitter component alongside the Rich Text Editor in Markdown mode. The preview pane is updated via updateValue() on every change event. 📄 Read: references/richtexteditor-integration.md for the full implementation.
Key Props / API
| API | Type | Default | Purpose |
|---|---|---|---|
MarkdownConverter.toHtml(content, options?) | static method | — | Converts Markdown string to HTML |
options.async | boolean | false | Async conversion for large content |
options.gfm | boolean | true | GitHub Flavored Markdown support |
options.lineBreak | boolean | false | Single line breaks → <br> |
options.silence | boolean | false | Suppress errors on invalid Markdown |
Configurable Options for Markdown Converter
The MarkdownConverter.toHtml() method accepts an optional second argument — a MarkdownConverterOptions object — that controls how Markdown is parsed and rendered.
Method Signature with Options
MarkdownConverter.toHtml(markdownContent: string, options?: MarkdownConverterOptions): string;MarkdownConverterOptions Reference
| Option | Type | Default | Description |
|---|---|---|---|
async | boolean | false | Enables asynchronous conversion for large content processing |
gfm | boolean | true | Enables GitHub Flavored Markdown (tables, strikethrough, task lists) |
lineBreak | boolean | false | Converts single line breaks into <br> elements |
silence | boolean | false | Suppresses errors — skips invalid Markdown instead of throwing |
---
Option Details
async — Asynchronous Conversion
Default: false
When true, the conversion runs asynchronously, preventing the main thread from being blocked when processing large Markdown documents.
import { MarkdownConverter } from '@syncfusion/ej2-markdown-converter';
const html = MarkdownConverter.toHtml(largeMarkdownContent, { async: true });Use async: true when converting documents with thousands of lines where synchronous conversion might cause UI freezes.
---
gfm — GitHub Flavored Markdown
Default: true
GitHub Flavored Markdown extends standard Markdown with:
- Tables using pipe syntax (
| col1 | col2 |) - Strikethrough using
~~text~~ - Task lists using
- [ ]and- [x] - Fenced code blocks with language hints (
`typescript)
// GFM enabled (default) — tables and strikethrough work
const html = MarkdownConverter.toHtml('| A | B |\n|---|---|\n| 1 | 2 |');
// Output: <table><thead>...</thead><tbody>...</tbody></table>
// GFM disabled — table rendered as plain text
const html = MarkdownConverter.toHtml('| A | B |\n|---|---|\n| 1 | 2 |', { gfm: false });Disable gfm only when you need strict CommonMark compatibility or want to avoid GFM-specific parsing behavior.
---
lineBreak — Single Line Break Conversion
Default: false
By default, a single line break in Markdown is not converted to <br> — a blank line is needed to start a new paragraph (standard Markdown behavior). Setting lineBreak: true converts every single newline into a <br> tag.
const markdown = 'Line one\nLine two';
// lineBreak: false (default)
MarkdownConverter.toHtml(markdown);
// Output: <p>Line one Line two</p>
// lineBreak: true
MarkdownConverter.toHtml(markdown, { lineBreak: true });
// Output: <p>Line one<br>Line two</p>Use lineBreak: true when your users expect line-by-line formatting, such as in poetry or structured text input.
---
silence — Error Suppression
Default: false
When false, invalid or malformed Markdown may throw an error. When true, errors are suppressed and the converter skips the problematic segment rather than throwing.
// Without silence — may throw on malformed input
const html = MarkdownConverter.toHtml(potentiallyBadMarkdown);
// With silence — safely skip bad segments
const html = MarkdownConverter.toHtml(potentiallyBadMarkdown, { silence: true });Use silence: true when processing user-submitted Markdown where the input quality cannot be guaranteed.
---
Using Multiple Options Together
Options can be combined freely:
import { MarkdownConverter } from '@syncfusion/ej2-markdown-converter';
const markdownContent = `
# Report
Line one
Line two
| Header A | Header B |
|----------|----------|
| Cell 1 | Cell 2 |
`;
const html = MarkdownConverter.toHtml(markdownContent, {
async: false,
gfm: true,
lineBreak: true,
silence: true
});
console.log(html);Full React Component Example
import React, { useRef, useState } from 'react';
import { RichTextEditorComponent, MarkdownEditor, Inject, Toolbar, Link, Image, Table } from '@syncfusion/ej2-react-richtexteditor';
import { MarkdownConverter } from '@syncfusion/ej2-markdown-converter';
function App() {
const rteRef = useRef<RichTextEditorComponent>(null);
const [previewHtml, setPreviewHtml] = useState('');
const handleChange = () => {
if (rteRef.current) {
const textarea = rteRef.current.contentModule.getEditPanel() as HTMLTextAreaElement;
const html = MarkdownConverter.toHtml(textarea.value, {
gfm: true,
lineBreak: false,
silence: true
}) as string;
setPreviewHtml(html);
}
};
return (
<div>
<RichTextEditorComponent
ref={rteRef}
editorMode="Markdown"
height="400px"
change={handleChange}
>
<Inject services={[MarkdownEditor, Toolbar, Link, Image, Table]} />
</RichTextEditorComponent>
<div dangerouslySetInnerHTML={{ __html: previewHtml }} />
</div>
);
}
export default App;Getting Started with Syncfusion React Markdown Converter
1. Install Syncfusion Packages
Install both the Markdown Converter and the Rich Text Editor packages:
npm install @syncfusion/ej2-markdown-converter
npm install @syncfusion/ej2-react-richtexteditorThe ej2-markdown-converter package provides the MarkdownConverter utility. The ej2-react-richtexteditor package provides the ejs-richtexteditor component used to author Markdown content.
2. Add CSS References
Add the following imports to src/styles.css. These load the material3 theme for all required Syncfusion sub-packages:
@import '../node_modules/@syncfusion/ej2-base/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-buttons/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-inputs/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-lists/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-navigations/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-popups/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-splitbuttons/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-richtexteditor/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-layouts/styles/material3.css';Note: ej2-layouts is only required if you are using the Splitter component for a side-by-side preview layout.3. Module Injection
The Rich Text Editor in Markdown mode uses a modular architecture — inject only the modules your feature requires. All modules are passed via the <Inject services={[...]} /> child component.
| Module | Purpose |
|---|---|
ToolbarService | Enables the editor toolbar |
LinkService | Enables hyperlink support in Markdown |
ImageService | Enables image insertion |
MarkdownEditorService | Activates Markdown editing mode |
TableService | Enables table insertion |
import {
ToolbarService,
LinkService,
ImageService,
MarkdownEditorService,
TableService
} from '@syncfusion/ej2-react-richtexteditor';4. Basic AppComponent
A minimal working example:
import React, { useRef, useEffect } from 'react';
import { RichTextEditorComponent, MarkdownEditor, Inject, Toolbar, Link, Image, Table, ToolbarSettingsModel, MarkdownFormatter } from '@syncfusion/ej2-react-richtexteditor';
import { MarkdownConverter } from '@syncfusion/ej2-markdown-converter';
function App() {
return (
<RichTextEditorComponent
editorMode="Markdown"
height="520px"
>
<Inject services={[MarkdownEditor, Toolbar, Image, Link, Table]} />
</RichTextEditorComponent>
);
}
export default App;5. Run the App
npm run devThe browser opens with the Markdown Editor. Click Preview in the toolbar to see the real-time HTML output of your Markdown content.
Common Setup Issues
- Missing CSS: If the editor renders unstyled, verify all
@importlines instyles.cssmatch the packages installed innode_modules/@syncfusion. - Module not found errors: Ensure both
@syncfusion/ej2-markdown-converterand@syncfusion/ej2-react-richtexteditorare listed inpackage.jsondependencies.
Integration Examples
Table of Contents
---
RichTextEditor with Preview Toggle
This example integrates MarkdownConverter with the Syncfusion RichTextEditorComponent in Markdown editor mode. A custom Preview toolbar button toggles between the Markdown source and the rendered HTML preview.
Required packages:
npm install @syncfusion/ej2-react-richtexteditor @syncfusion/ej2-markdown-converter @syncfusion/ej2-baseRequired CSS imports:
import '@syncfusion/ej2-react-richtexteditor/styles/material.css';import * as React from 'react';
import { useRef } from 'react';
import {
RichTextEditorComponent, Inject, Link, Image,
MarkdownEditor, Toolbar, Table
} from '@syncfusion/ej2-react-richtexteditor';
import { MarkdownFormatter, KeyboardEventArgs } from '@syncfusion/ej2-richtexteditor';
import { createElement } from '@syncfusion/ej2-base';
import { MarkdownConverter } from '@syncfusion/ej2-markdown-converter';
function App() {
const rteRef = useRef<RichTextEditorComponent>(null);
let mdsource: HTMLElement;
let textArea: HTMLTextAreaElement;
const toolbarSettings = {
items: [
'Bold', 'Italic', 'StrikeThrough', '|',
'Formats', 'Blockquote', 'OrderedList', 'UnorderedList',
'SuperScript', 'SubScript', '|',
'CreateLink', 'Image', 'CreateTable', '|',
{
tooltipText: 'Preview',
template:
'<button id="preview-code" class="e-tbar-btn e-control e-btn e-icon-btn" aria-label="Preview Code">' +
'<span class="e-btn-icon e-md-preview e-icons"></span></button>'
},
'|', 'Undo', 'Redo'
]
};
// Called on every keyup — updates preview if it is currently active
function markDownConversion(): void {
if (mdsource.classList.contains('e-active')) {
const id = rteRef.current!.getID() + 'html-view';
const htmlPreview = rteRef.current!.element.querySelector('#' + id) as HTMLElement;
htmlPreview.innerHTML = MarkdownConverter.toHtml(
(rteRef.current!.contentModule.getEditPanel() as HTMLTextAreaElement).value
) as string;
}
}
// Toggles between Markdown source view and HTML preview
function fullPreview(): void {
const id = rteRef.current!.getID() + 'html-preview';
let htmlPreview = rteRef.current!.element.querySelector('#' + id) as HTMLElement;
const previewTextArea = rteRef.current!.element.querySelector('.e-rte-content') as HTMLElement;
if (mdsource.classList.contains('e-active')) {
// Switch back to Markdown source
mdsource.classList.remove('e-active');
mdsource.parentElement!.title = 'Preview';
textArea.style.display = 'block';
htmlPreview.style.display = 'none';
previewTextArea.style.overflow = 'hidden';
} else {
// Switch to HTML preview
mdsource.classList.add('e-active');
if (!htmlPreview) {
htmlPreview = createElement('div', { className: 'e-content e-pre-source' });
htmlPreview.id = id;
textArea.parentNode!.appendChild(htmlPreview);
previewTextArea.style.overflow = 'auto';
}
if (previewTextArea.style.overflow === 'hidden') {
previewTextArea.style.overflow = 'auto';
}
textArea.style.display = 'none';
htmlPreview.style.display = 'block';
htmlPreview.innerHTML = MarkdownConverter.toHtml(
(rteRef.current!.contentModule.getEditPanel() as HTMLTextAreaElement).value
) as string;
mdsource.parentElement!.title = 'Code View';
}
}
const onCreate = () => {
textArea = rteRef.current!.contentModule.getEditPanel() as HTMLTextAreaElement;
// Update preview on every keyup
textArea.addEventListener('keyup', (e: KeyboardEventArgs) => {
markDownConversion();
});
mdsource = document.getElementById('preview-code')!;
mdsource.addEventListener('click', (e: MouseEvent) => {
fullPreview();
// Disable editing toolbar items when in preview mode
if ((e.currentTarget as HTMLElement).classList.contains('e-active')) {
rteRef.current!.disableToolbarItem([
'Bold', 'Italic', 'StrikeThrough', 'OrderedList', 'UnorderedList',
'SuperScript', 'SubScript', 'CreateLink', 'Image', 'CreateTable',
'Formats', 'Blockquote', 'Undo', 'Redo'
]);
} else {
rteRef.current!.enableToolbarItem([
'Bold', 'Italic', 'StrikeThrough', 'OrderedList', 'UnorderedList',
'SuperScript', 'SubScript', 'CreateLink', 'Image', 'CreateTable',
'Formats', 'Blockquote', 'Undo', 'Redo'
]);
}
});
};
return (
<RichTextEditorComponent
id="markdown-editor"
ref={rteRef}
height="520px"
value="# Welcome\nType **Markdown** here and click Preview to see the HTML output."
placeholder="Enter your Markdown here..."
formatter={new MarkdownFormatter({ listTags: { 'OL': '1., 2., 3.' } })}
editorMode="Markdown"
toolbarSettings={toolbarSettings}
created={onCreate}
>
<Inject services={[Link, Image, MarkdownEditor, Toolbar, Table]} />
</RichTextEditorComponent>
);
}
export default App;Key points:
editorMode="Markdown"enables Markdown editing mode in the RichTextEditor.- The
createdcallback wires up the keyup listener and Preview button click handler. disableToolbarItem/enableToolbarItemprevents editing while in preview mode.- The HTML preview
divis created lazily on first toggle.
---
Side-by-Side Editor and Preview with Splitter
This example uses the Syncfusion SplitterComponent to display the RichTextEditor (Markdown mode) on the left and a live HTML preview on the right, with automatic conversion on every change.
Required packages:
npm install @syncfusion/ej2-react-richtexteditor @syncfusion/ej2-react-layouts @syncfusion/ej2-markdown-converter @syncfusion/ej2-baseRequired CSS imports:
import '@syncfusion/ej2-react-richtexteditor/styles/material.css';
import '@syncfusion/ej2-react-layouts/styles/material.css';import * as React from 'react';
import { useRef } from 'react';
import {
RichTextEditorComponent, Inject, Link, Image,
MarkdownEditor, Toolbar, Table, ToolbarType
} from '@syncfusion/ej2-react-richtexteditor';
import {
SplitterComponent, PanesDirective, PaneDirective
} from '@syncfusion/ej2-react-layouts';
import { Browser } from '@syncfusion/ej2-base';
import { MarkdownConverter } from '@syncfusion/ej2-markdown-converter';
function App() {
const rteRef = useRef<RichTextEditorComponent>(null);
const splitterRef = useRef<SplitterComponent>(null);
const srcAreaRef = useRef<HTMLDivElement>(null);
const toolbarSettings = {
type: ToolbarType.Expand,
enableFloating: false,
items: [
'Bold', 'Italic', 'StrikeThrough', '|',
'Formats', 'Blockquote', 'OrderedList', 'UnorderedList', '|',
'CreateLink', 'Image', 'CreateTable', '|',
'Undo', 'Redo'
]
};
// Converts current editor content and updates the preview pane
const updateValue = () => {
if (srcAreaRef.current && rteRef.current) {
srcAreaRef.current.innerHTML = MarkdownConverter.toHtml(
(rteRef.current.contentModule.getEditPanel() as HTMLTextAreaElement).value,
{ async: true, gfm: true, lineBreak: true, silence: true }
) as string;
}
};
const onCreate = () => {
updateValue(); // Render initial content
// Switch splitter to vertical layout on mobile devices
if (Browser.isDevice && splitterRef.current) {
splitterRef.current.orientation = 'Vertical';
}
};
// Refresh RTE layout when the splitter is resized
const onResizing = () => {
if (rteRef.current) {
rteRef.current.refreshUI();
}
};
return (
<SplitterComponent
id="splitter-rte-markdown-preview"
ref={splitterRef}
height="450px"
width="100%"
resizing={onResizing}
>
<PanesDirective>
<PaneDirective
resizable={true}
size="50%"
min="40%"
content={() => (
<RichTextEditorComponent
id="markdown-editor"
ref={rteRef}
height="100%"
value="# Welcome\nEdit this **Markdown** content and see the live preview on the right."
placeholder="Enter your Markdown here..."
floatingToolbarOffset={0}
editorMode="Markdown"
toolbarSettings={toolbarSettings}
saveInterval={1}
actionComplete={updateValue}
change={updateValue}
created={onCreate}
>
<Inject services={[Link, Image, MarkdownEditor, Toolbar, Table]} />
</RichTextEditorComponent>
)}
/>
<PaneDirective
min="40%"
content={() => (
<div
className="source-code"
ref={srcAreaRef}
style={{ padding: '10px' }}
/>
)}
/>
</PanesDirective>
</SplitterComponent>
);
}
export default App;Key points:
actionCompleteandchangeevents both triggerupdateValuefor live updates.saveInterval={1}ensures the RTE value updates frequently for near-real-time preview.Browser.isDeviceswitches the Splitter to vertical orientation on mobile.onResizingcallsrefreshUI()to prevent layout issues when the splitter is dragged.- All four
MarkdownConverterOptionsare passed for robust user-input handling.
MarkdownConverter.toHtml() API
The toHtml method is the core API of the Syncfusion Markdown Converter. It converts a Markdown string into clean, semantic HTML.
Method Signature
MarkdownConverter.toHtml(
markdownContent: string,
options?: MarkdownConverterOptions
): string| Parameter | Type | Required | Description |
|---|---|---|---|
markdownContent | string | ✓ | The Markdown text to convert |
options | MarkdownConverterOptions | ✗ | Optional configuration (see configurable-options.md) |
Returns: string — the converted HTML markup.
Import
import { MarkdownConverter } from '@syncfusion/ej2-markdown-converter';Basic Usage
import { MarkdownConverter } from '@syncfusion/ej2-markdown-converter';
const markdownContent = '# Hello World\nThis is **Markdown** text.';
const htmlOutput = MarkdownConverter.toHtml(markdownContent);
console.log(htmlOutput);
// Output: <h1>Hello World</h1><p>This is <strong>Markdown</strong> text.</p>Supported Markdown Elements
toHtml handles all common Markdown syntax out of the box:
| Markdown Syntax | HTML Output |
|---|---|
# Heading 1 | <h1>Heading 1</h1> |
## Heading 2 | <h2>Heading 2</h2> |
**bold** | <strong>bold</strong> |
*italic* or _italic_ | <em>italic</em> |
~~strikethrough~~ | <del>strikethrough</del> |
[link](url) | <a href="url">link</a> |
 | <img alt="alt" src="src"> |
` inline code ` | <code>inline code</code> |
> blockquote | <blockquote>blockquote</blockquote> |
- item or + item | <ul><li>item</li></ul> |
1. item | <ol><li>item</li></ol> |
--- | <hr> |
| GFM tables | <table>...</table> |
Fenced code blocks ( ` ) | <pre><code>...</code></pre> |
GitHub Flavored Markdown (GFM) is enabled by default, which adds support for tables, strikethrough, and task lists. Disable it by passing { gfm: false } in options.Using the Return Value in React
Binding to dangerouslySetInnerHTML
The most common usage is setting the innerHTML of a preview element:
import React, { useState } from 'react';
import { MarkdownConverter } from '@syncfusion/ej2-markdown-converter';
function Preview() {
const [previewHtml, setPreviewHtml] = useState('');
const onInput = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const html = MarkdownConverter.toHtml(e.target.value) as string;
setPreviewHtml(html);
};
return (
<>
<textarea onInput={onInput} />
<div dangerouslySetInnerHTML={{ __html: previewHtml }} />
</>
);
}Security note: When binding converted HTML to[innerHTML], Use React'sdangerouslySetInnerHTMLonly for trusted content. For user-generated content from unknown sources, sanitize the HTML output before binding.
Direct DOM Manipulation
When working with the Rich Text Editor, direct DOM assignment is typical:
const previewEl = document.getElementById('html-preview') as HTMLElement;
previewEl.innerHTML = MarkdownConverter.toHtml(markdownString) as string;Edge Cases
- Empty string:
toHtml('')returns an empty string — no error thrown. - Invalid Markdown: By default, the converter attempts best-effort conversion. Pass
{ silence: true }to suppress any errors on malformed input. - Large content: For large documents, pass
{ async: true }to avoid blocking the main thread. - Custom list syntax: If using
MarkdownFormatterwith custom list tags (e.g.,'+ 'for unordered), thetoHtmloutput reflects those custom tags correctly.