or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

tessl/npm-react-markdown

React component to render markdown safely with plugin support and custom component mapping

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/react-markdown@10.1.x

To install, run

npx @tessl/cli install tessl/npm-react-markdown@10.1.0

0

# React Markdown

1

2

React Markdown is a React component library for safely rendering markdown content as React elements. It transforms markdown text into React JSX elements using the unified/remark ecosystem, offering comprehensive markdown parsing and rendering capabilities with support for CommonMark and GitHub Flavored Markdown through plugins. The library prioritizes security by avoiding dangerouslySetInnerHTML and XSS vulnerabilities, provides extensive customization through component mapping, and integrates seamlessly with the remark/rehype plugin ecosystem.

3

4

## Package Information

5

6

- **Package Name**: react-markdown

7

- **Package Type**: npm

8

- **Language**: JavaScript/TypeScript (ES Modules)

9

- **Installation**: `npm install react-markdown`

10

11

## Core Imports

12

13

```typescript

14

import Markdown, { MarkdownAsync, MarkdownHooks, defaultUrlTransform } from "react-markdown";

15

```

16

17

Individual imports:

18

19

```typescript

20

import { default as Markdown, MarkdownAsync, MarkdownHooks, defaultUrlTransform } from "react-markdown";

21

```

22

23

For CommonJS:

24

25

```javascript

26

const Markdown = require("react-markdown").default;

27

const { MarkdownAsync, MarkdownHooks, defaultUrlTransform } = require("react-markdown");

28

```

29

30

## Basic Usage

31

32

```typescript

33

import React from "react";

34

import Markdown from "react-markdown";

35

36

function App() {

37

const markdown = `

38

# Hello World

39

40

This is **bold text** and this is *italic text*.

41

42

- List item 1

43

- List item 2

44

45

[Link to example](https://example.com)

46

`;

47

48

return (

49

<div>

50

<Markdown>{markdown}</Markdown>

51

</div>

52

);

53

}

54

```

55

56

## Architecture

57

58

React Markdown is built around several key components:

59

60

- **Processing Pipeline**: Uses unified ecosystem (remark β†’ rehype β†’ React) for reliable markdown parsing

61

- **Security-First**: No dangerouslySetInnerHTML usage, built-in URL sanitization, element filtering

62

- **Component Mapping**: Replace any HTML element with custom React components

63

- **Plugin System**: Extensive plugin support for both markdown (remark) and HTML (rehype) processing

64

- **Multiple Rendering Modes**: Synchronous, asynchronous server-side, and client-side hooks support

65

66

## Capabilities

67

68

### Synchronous Markdown Rendering

69

70

Main component for rendering markdown synchronously. Best for most use cases where no async plugins are needed.

71

72

```typescript { .api }

73

/**

74

* Synchronous React component to render markdown content

75

* @param options - Configuration options including markdown content and processing settings

76

* @returns React element containing the rendered markdown as React components

77

*/

78

function Markdown(options: Readonly<Options>): ReactElement;

79

```

80

81

### Asynchronous Server-Side Rendering

82

83

Component for server-side rendering with async plugin support.

84

85

```typescript { .api }

86

/**

87

* Asynchronous React component for server-side rendering with async plugin support

88

* @param options - Configuration options including markdown content and async plugins

89

* @returns Promise that resolves to a React element with rendered markdown

90

*/

91

function MarkdownAsync(options: Readonly<Options>): Promise<ReactElement>;

92

```

93

94

### Client-Side Hooks Rendering

95

96

Component using React hooks for client-side async plugin support.

97

98

```typescript { .api }

99

/**

100

* React component using hooks for client-side async plugin processing

101

* @param options - Extended configuration options with fallback content support

102

* @returns React node - either the rendered markdown or fallback content during processing

103

*/

104

function MarkdownHooks(options: Readonly<HooksOptions>): ReactNode;

105

```

106

107

**Usage Example:**

108

109

```typescript

110

import React from "react";

111

import { MarkdownHooks } from "react-markdown";

112

113

function AsyncMarkdown({ content }: { content: string }) {

114

return (

115

<MarkdownHooks

116

fallback={<div>Loading markdown...</div>}

117

remarkPlugins={[/* async plugins */]}

118

>

119

{content}

120

</MarkdownHooks>

121

);

122

}

123

```

124

125

### URL Transformation

126

127

Default URL sanitization function to prevent XSS attacks.

128

129

```typescript { .api }

130

/**

131

* Default URL sanitization function that prevents XSS attacks by validating URL protocols

132

* @param value - The URL string to sanitize and validate

133

* @returns The original URL if safe (relative or using allowed protocols), empty string if unsafe

134

*/

135

function defaultUrlTransform(value: string): string;

136

```

137

138

**Usage Example:**

139

140

```typescript

141

import Markdown, { defaultUrlTransform } from "react-markdown";

142

143

// Custom URL transformer

144

function customUrlTransform(url: string, key: string, node: Element): string {

145

// Apply default sanitization first

146

const safe = defaultUrlTransform(url);

147

148

// Add custom logic

149

if (key === 'href' && safe.startsWith('/')) {

150

return `https://mysite.com${safe}`;

151

}

152

153

return safe;

154

}

155

156

<Markdown urlTransform={customUrlTransform}>

157

[Internal link](/docs/api)

158

</Markdown>

159

```

160

161

### Component Customization

162

163

Replace default HTML elements with custom React components.

164

165

```typescript { .api }

166

interface Components {

167

[Key in keyof JSX.IntrinsicElements]?:

168

| ComponentType<JSX.IntrinsicElements[Key] & ExtraProps>

169

| keyof JSX.IntrinsicElements;

170

}

171

172

interface ExtraProps {

173

/** Original HAST element (when passNode is enabled) */

174

node?: Element | undefined;

175

}

176

```

177

178

**Usage Example:**

179

180

```typescript

181

import React from "react";

182

import Markdown from "react-markdown";

183

184

const components = {

185

// Custom heading component

186

h1: ({ children, ...props }) => (

187

<h1 className="custom-heading" {...props}>

188

🎯 {children}

189

</h1>

190

),

191

192

// Custom code block

193

code: ({ inline, className, children, ...props }) => {

194

const match = /language-(\w+)/.exec(className || '');

195

return !inline && match ? (

196

<SyntaxHighlighter language={match[1]} {...props}>

197

{String(children).replace(/\n$/, '')}

198

</SyntaxHighlighter>

199

) : (

200

<code className={className} {...props}>

201

{children}

202

</code>

203

);

204

},

205

206

// Custom link component

207

a: ({ href, children }) => (

208

<a href={href} target="_blank" rel="noopener noreferrer">

209

{children} πŸ”—

210

</a>

211

)

212

};

213

214

<Markdown components={components}>

215

# Hello World

216

217

Check out this [link](https://example.com)

218

219

\`\`\`javascript

220

console.log("Hello!");

221

\`\`\`

222

</Markdown>

223

```

224

225

### Element Filtering

226

227

Control which HTML elements are allowed in the rendered output.

228

229

```typescript { .api }

230

/**

231

* Callback function to filter elements during processing

232

* @param element - The HAST element to check for inclusion

233

* @param index - The index of the element within its parent's children array

234

* @param parent - The parent HAST element containing this element, or undefined for root elements

235

* @returns true to allow the element, false to remove it, null/undefined defaults to false

236

*/

237

type AllowElement = (

238

element: Readonly<Element>,

239

index: number,

240

parent: Readonly<Parents> | undefined

241

) => boolean | null | undefined;

242

```

243

244

**Usage Example:**

245

246

```typescript

247

import Markdown from "react-markdown";

248

249

// Only allow safe elements

250

<Markdown

251

allowedElements={['p', 'strong', 'em', 'ul', 'ol', 'li', 'h1', 'h2', 'h3']}

252

>

253

# Safe Markdown

254

255

This **bold text** is allowed, but <script>alert('xss')</script> is not.

256

</Markdown>

257

258

// Custom filtering logic

259

<Markdown

260

allowElement={(element, index, parent) => {

261

// Disallow images in list items

262

if (element.tagName === 'img' && parent?.tagName === 'li') {

263

return false;

264

}

265

return true;

266

}}

267

>

268

- This list item can have images: ![alt](image.jpg)

269

- This one will have the image filtered out

270

</Markdown>

271

```

272

273

### Plugin System

274

275

Extend markdown processing with remark and rehype plugins.

276

277

```typescript { .api }

278

/**

279

* URL transformation callback for sanitizing and modifying URLs

280

* @param url - The original URL string to transform

281

* @param key - The HTML property name containing the URL (e.g., 'href', 'src', 'cite')

282

* @param node - The HAST element node containing the URL property

283

* @returns The transformed URL string, or null/undefined to remove the URL

284

*/

285

type UrlTransform = (

286

url: string,

287

key: string,

288

node: Readonly<Element>

289

) => string | null | undefined;

290

```

291

292

**Usage Example:**

293

294

```typescript

295

import Markdown from "react-markdown";

296

import remarkGfm from "remark-gfm";

297

import remarkToc from "remark-toc";

298

import remarkMath from "remark-math";

299

import rehypeRaw from "rehype-raw";

300

import rehypeHighlight from "rehype-highlight";

301

import rehypeKatex from "rehype-katex";

302

import rehypeSlug from "rehype-slug";

303

304

// Basic plugin usage

305

<Markdown

306

remarkPlugins={[

307

remarkGfm, // GitHub Flavored Markdown

308

remarkToc // Table of contents

309

]}

310

rehypePlugins={[

311

rehypeRaw, // Allow raw HTML

312

rehypeHighlight // Syntax highlighting

313

]}

314

remarkRehypeOptions={{

315

allowDangerousHtml: true

316

}}

317

>

318

# My Document

319

320

## Table of Contents

321

322

| Feature | Supported |

323

|---------|-----------|

324

| Tables | βœ… |

325

| Lists | βœ… |

326

327

~~Strikethrough~~ text is supported with remarkGfm.

328

329

```javascript

330

// This will be syntax highlighted

331

console.log("Hello World!");

332

```

333

</Markdown>

334

335

// Advanced plugin configuration with options

336

<Markdown

337

remarkPlugins={[

338

remarkGfm,

339

[remarkToc, { heading: "contents", maxDepth: 3 }],

340

[remarkMath, { singleDollarTextMath: false }]

341

]}

342

rehypePlugins={[

343

rehypeSlug,

344

[rehypeHighlight, {

345

detect: true,

346

ignoreMissing: true,

347

subset: ['javascript', 'typescript', 'css']

348

}],

349

[rehypeKatex, {

350

strict: false,

351

trust: false,

352

macros: {

353

"\\RR": "\\mathbb{R}",

354

"\\NN": "\\mathbb{N}"

355

}

356

}]

357

]}

358

remarkRehypeOptions={{

359

allowDangerousHtml: true,

360

clobberPrefix: 'user-content-',

361

footnoteLabel: 'Footnotes',

362

footnoteLabelTagName: 'h2'

363

}}

364

>

365

# Mathematical Document

366

367

## Contents

368

369

Inline math: $E = mc^2$ and display math:

370

371

$$

372

\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}

373

$$

374

375

```typescript

376

// TypeScript code with syntax highlighting

377

interface User {

378

id: number;

379

name: string;

380

}

381

```

382

</Markdown>

383

384

// Custom plugin example

385

function customRemarkPlugin() {

386

return (tree, file) => {

387

// Transform AST nodes

388

visit(tree, 'text', (node) => {

389

node.value = node.value.replace(/TODO:/g, 'πŸ“‹ TODO:');

390

});

391

};

392

}

393

394

<Markdown

395

remarkPlugins={[

396

customRemarkPlugin,

397

[remarkGfm, { singleTilde: false }]

398

]}

399

>

400

TODO: This will be prefixed with an emoji

401

402

~~This strikethrough requires double tildes~~

403

</Markdown>

404

```

405

406

## External Type Dependencies

407

408

```typescript { .api }

409

// From 'hast' package

410

interface Element {

411

type: 'element';

412

tagName: string;

413

properties: Properties;

414

children: Array<Element | Text | Comment>;

415

position?: Position;

416

}

417

418

interface Properties {

419

[key: string]: boolean | number | string | null | undefined | Array<string | number>;

420

}

421

422

interface Text {

423

type: 'text';

424

value: string;

425

position?: Position;

426

}

427

428

interface Comment {

429

type: 'comment';

430

value: string;

431

position?: Position;

432

}

433

434

interface Parents {

435

type: string;

436

children: Array<Element | Text | Comment>;

437

}

438

439

interface Position {

440

start: Point;

441

end: Point;

442

}

443

444

interface Point {

445

line: number;

446

column: number;

447

offset?: number;

448

}

449

450

// From 'unified' package

451

type PluggableList = Array<Pluggable>;

452

type Pluggable = Plugin | Preset | [Plugin, ...Parameters] | [Preset, ...Parameters];

453

type Plugin = (this: Processor, ...parameters: any[]) => Transformer | void;

454

type Preset = { plugins: PluggableList; settings?: Settings };

455

type Transformer = (tree: any, file: any, next?: Function) => any;

456

type Parameters = any[];

457

type Settings = Record<string, any>;

458

459

interface Processor {

460

use(...args: any[]): Processor;

461

parse(document: string | Buffer): any;

462

run(tree: any, file?: any, callback?: Function): any;

463

runSync(tree: any, file?: any): any;

464

}

465

466

// From 'remark-rehype' package

467

interface RemarkRehypeOptions {

468

allowDangerousHtml?: boolean;

469

passThrough?: Array<string>;

470

handlers?: Record<string, Function>;

471

unknownHandler?: Function;

472

clobberPrefix?: string;

473

footnoteLabel?: string;

474

footnoteLabelTagName?: string;

475

footnoteLabelProperties?: Properties;

476

footnoteBackLabel?: string;

477

}

478

479

// From 'vfile' package

480

interface VFile {

481

value: string | Buffer;

482

path?: string;

483

basename?: string;

484

stem?: string;

485

extname?: string;

486

dirname?: string;

487

history: Array<string>;

488

messages: Array<VFileMessage>;

489

data: Record<string, any>;

490

}

491

492

interface VFileMessage {

493

reason: string;

494

line?: number;

495

column?: number;

496

position?: Position;

497

ruleId?: string;

498

source?: string;

499

fatal?: boolean;

500

}

501

```

502

503

## Configuration Options

504

505

```typescript { .api }

506

interface Options {

507

/**

508

* Markdown content to render as a string

509

* @default undefined

510

*/

511

children?: string | null | undefined;

512

513

/**

514

* Custom callback function to programmatically filter elements during processing

515

* Called after allowedElements/disallowedElements filtering

516

* @default undefined

517

*/

518

allowElement?: AllowElement | null | undefined;

519

520

/**

521

* Array of HTML tag names to allow in output (whitelist approach)

522

* Cannot be used together with disallowedElements

523

* @default undefined (allows all elements)

524

*/

525

allowedElements?: ReadonlyArray<string> | null | undefined;

526

527

/**

528

* Array of HTML tag names to remove from output (blacklist approach)

529

* Cannot be used together with allowedElements

530

* @default []

531

*/

532

disallowedElements?: ReadonlyArray<string> | null | undefined;

533

534

/**

535

* Object mapping HTML tag names to custom React components

536

* @default undefined

537

*/

538

components?: Components | null | undefined;

539

540

/**

541

* Array of remark plugins to process markdown before converting to HTML

542

* Plugins can be functions or [plugin, options] tuples

543

* @default []

544

*/

545

remarkPlugins?: PluggableList | null | undefined;

546

547

/**

548

* Array of rehype plugins to process HTML after markdown conversion

549

* Plugins can be functions or [plugin, options] tuples

550

* @default []

551

*/

552

rehypePlugins?: PluggableList | null | undefined;

553

554

/**

555

* Configuration options passed to remark-rehype for markdown to HTML conversion

556

* @default { allowDangerousHtml: true }

557

*/

558

remarkRehypeOptions?: Readonly<RemarkRehypeOptions> | null | undefined;

559

560

/**

561

* Whether to completely ignore HTML in markdown input

562

* When true, HTML tags are treated as plain text

563

* @default false

564

*/

565

skipHtml?: boolean | null | undefined;

566

567

/**

568

* Whether to extract children from disallowed elements instead of removing entirely

569

* When false, disallowed elements and their children are removed

570

* When true, only the element wrapper is removed, children are kept

571

* @default false

572

*/

573

unwrapDisallowed?: boolean | null | undefined;

574

575

/**

576

* Function to transform/sanitize URLs in href, src, and other URL attributes

577

* Receives the URL, attribute name, and containing element

578

* @default defaultUrlTransform

579

*/

580

urlTransform?: UrlTransform | null | undefined;

581

}

582

583

interface HooksOptions extends Options {

584

/**

585

* React content to display while markdown is being processed asynchronously

586

* Only used by MarkdownHooks component for client-side async processing

587

* @default undefined

588

*/

589

fallback?: ReactNode | null | undefined;

590

}

591

```

592

593

## Security Features

594

595

React Markdown prioritizes security with multiple built-in protections:

596

597

- **No dangerouslySetInnerHTML**: All content is processed through React's virtual DOM

598

- **URL Sanitization**: Default `defaultUrlTransform` prevents XSS via malicious URLs

599

- **Element Filtering**: Control exactly which HTML elements are allowed

600

- **Plugin Sandboxing**: Plugins operate within the unified processing pipeline

601

602

**Security Example:**

603

604

```typescript

605

import Markdown, { defaultUrlTransform } from "react-markdown";

606

607

// Highly secure configuration

608

<Markdown

609

// Only allow safe elements

610

allowedElements={[

611

'p', 'strong', 'em', 'ul', 'ol', 'li',

612

'h1', 'h2', 'h3', 'h4', 'h5', 'h6',

613

'blockquote', 'code', 'pre'

614

]}

615

616

// Skip any raw HTML

617

skipHtml={true}

618

619

// Use default URL sanitization

620

urlTransform={defaultUrlTransform}

621

>

622

{untrustedMarkdown}

623

</Markdown>

624

```

625

626

## Error Handling

627

628

React Markdown handles various error conditions gracefully:

629

630

- **Invalid markdown**: Renders as much as possible, invalid syntax is treated as text

631

- **Plugin errors**: Async components (MarkdownHooks, MarkdownAsync) will throw errors

632

- **Type validation**: Deprecated props trigger runtime errors with helpful messages

633

- **Configuration conflicts**: Mutually exclusive options (e.g., allowedElements + disallowedElements) cause errors

634

635

**Error Handling Example:**

636

637

```typescript

638

import React from "react";

639

import { MarkdownHooks } from "react-markdown";

640

641

function SafeMarkdown({ content }: { content: string }) {

642

return (

643

<ErrorBoundary fallback={<div>Failed to render markdown</div>}>

644

<MarkdownHooks

645

fallback={<div>Loading...</div>}

646

remarkPlugins={[/* potentially failing async plugins */]}

647

>

648

{content}

649

</MarkdownHooks>

650

</ErrorBoundary>

651

);

652

}

653

```