or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

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

index.mddocs/

React Markdown

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.

Package Information

  • Package Name: react-markdown
  • Package Type: npm
  • Language: JavaScript/TypeScript (ES Modules)
  • Installation: npm install react-markdown

Core Imports

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

Individual imports:

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

For CommonJS:

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

Basic Usage

import React from "react";
import Markdown from "react-markdown";

function App() {
  const markdown = `
# Hello World

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

- List item 1
- List item 2

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

  return (
    <div>
      <Markdown>{markdown}</Markdown>
    </div>
  );
}

Architecture

React Markdown is built around several key components:

  • Processing Pipeline: Uses unified ecosystem (remark β†’ rehype β†’ React) for reliable markdown parsing
  • Security-First: No dangerouslySetInnerHTML usage, built-in URL sanitization, element filtering
  • Component Mapping: Replace any HTML element with custom React components
  • Plugin System: Extensive plugin support for both markdown (remark) and HTML (rehype) processing
  • Multiple Rendering Modes: Synchronous, asynchronous server-side, and client-side hooks support

Capabilities

Synchronous Markdown Rendering

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

/**
 * Synchronous React component to render markdown content
 * @param options - Configuration options including markdown content and processing settings
 * @returns React element containing the rendered markdown as React components
 */
function Markdown(options: Readonly<Options>): ReactElement;

Asynchronous Server-Side Rendering

Component for server-side rendering with async plugin support.

/**
 * Asynchronous React component for server-side rendering with async plugin support
 * @param options - Configuration options including markdown content and async plugins
 * @returns Promise that resolves to a React element with rendered markdown
 */
function MarkdownAsync(options: Readonly<Options>): Promise<ReactElement>;

Client-Side Hooks Rendering

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

/**
 * React component using hooks for client-side async plugin processing
 * @param options - Extended configuration options with fallback content support
 * @returns React node - either the rendered markdown or fallback content during processing
 */
function MarkdownHooks(options: Readonly<HooksOptions>): ReactNode;

Usage Example:

import React from "react";
import { MarkdownHooks } from "react-markdown";

function AsyncMarkdown({ content }: { content: string }) {
  return (
    <MarkdownHooks
      fallback={<div>Loading markdown...</div>}
      remarkPlugins={[/* async plugins */]}
    >
      {content}
    </MarkdownHooks>
  );
}

URL Transformation

Default URL sanitization function to prevent XSS attacks.

/**
 * Default URL sanitization function that prevents XSS attacks by validating URL protocols
 * @param value - The URL string to sanitize and validate
 * @returns The original URL if safe (relative or using allowed protocols), empty string if unsafe
 */
function defaultUrlTransform(value: string): string;

Usage Example:

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

// Custom URL transformer
function customUrlTransform(url: string, key: string, node: Element): string {
  // Apply default sanitization first
  const safe = defaultUrlTransform(url);
  
  // Add custom logic
  if (key === 'href' && safe.startsWith('/')) {
    return `https://mysite.com${safe}`;
  }
  
  return safe;
}

<Markdown urlTransform={customUrlTransform}>
  [Internal link](/docs/api)
</Markdown>

Component Customization

Replace default HTML elements with custom React components.

interface Components {
  [Key in keyof JSX.IntrinsicElements]?: 
    | ComponentType<JSX.IntrinsicElements[Key] & ExtraProps> 
    | keyof JSX.IntrinsicElements;
}

interface ExtraProps {
  /** Original HAST element (when passNode is enabled) */
  node?: Element | undefined;
}

Usage Example:

import React from "react";
import Markdown from "react-markdown";

const components = {
  // Custom heading component
  h1: ({ children, ...props }) => (
    <h1 className="custom-heading" {...props}>
      🎯 {children}
    </h1>
  ),
  
  // Custom code block
  code: ({ inline, className, children, ...props }) => {
    const match = /language-(\w+)/.exec(className || '');
    return !inline && match ? (
      <SyntaxHighlighter language={match[1]} {...props}>
        {String(children).replace(/\n$/, '')}
      </SyntaxHighlighter>
    ) : (
      <code className={className} {...props}>
        {children}
      </code>
    );
  },
  
  // Custom link component
  a: ({ href, children }) => (
    <a href={href} target="_blank" rel="noopener noreferrer">
      {children} πŸ”—
    </a>
  )
};

<Markdown components={components}>
  # Hello World
  
  Check out this [link](https://example.com)
  
  \`\`\`javascript
  console.log("Hello!");
  \`\`\`
</Markdown>

Element Filtering

Control which HTML elements are allowed in the rendered output.

/**
 * Callback function to filter elements during processing
 * @param element - The HAST element to check for inclusion
 * @param index - The index of the element within its parent's children array
 * @param parent - The parent HAST element containing this element, or undefined for root elements
 * @returns true to allow the element, false to remove it, null/undefined defaults to false
 */
type AllowElement = (
  element: Readonly<Element>,
  index: number,
  parent: Readonly<Parents> | undefined
) => boolean | null | undefined;

Usage Example:

import Markdown from "react-markdown";

// Only allow safe elements
<Markdown
  allowedElements={['p', 'strong', 'em', 'ul', 'ol', 'li', 'h1', 'h2', 'h3']}
>
  # Safe Markdown
  
  This **bold text** is allowed, but <script>alert('xss')</script> is not.
</Markdown>

// Custom filtering logic
<Markdown
  allowElement={(element, index, parent) => {
    // Disallow images in list items
    if (element.tagName === 'img' && parent?.tagName === 'li') {
      return false;
    }
    return true;
  }}
>
  - This list item can have images: ![alt](image.jpg)
  - This one will have the image filtered out
</Markdown>

Plugin System

Extend markdown processing with remark and rehype plugins.

/**
 * URL transformation callback for sanitizing and modifying URLs
 * @param url - The original URL string to transform
 * @param key - The HTML property name containing the URL (e.g., 'href', 'src', 'cite')
 * @param node - The HAST element node containing the URL property
 * @returns The transformed URL string, or null/undefined to remove the URL
 */
type UrlTransform = (
  url: string,
  key: string,
  node: Readonly<Element>
) => string | null | undefined;

Usage Example:

import Markdown from "react-markdown";
import remarkGfm from "remark-gfm";
import remarkToc from "remark-toc";
import remarkMath from "remark-math";
import rehypeRaw from "rehype-raw";
import rehypeHighlight from "rehype-highlight";
import rehypeKatex from "rehype-katex";
import rehypeSlug from "rehype-slug";

// Basic plugin usage
<Markdown
  remarkPlugins={[
    remarkGfm,    // GitHub Flavored Markdown
    remarkToc     // Table of contents
  ]}
  rehypePlugins={[
    rehypeRaw,      // Allow raw HTML
    rehypeHighlight // Syntax highlighting
  ]}
  remarkRehypeOptions={{
    allowDangerousHtml: true
  }}
>
  # My Document
  
  ## Table of Contents
  
  | Feature | Supported |
  |---------|-----------|
  | Tables  | βœ…        |
  | Lists   | βœ…        |
  
  ~~Strikethrough~~ text is supported with remarkGfm.
  
  ```javascript
  // This will be syntax highlighted
  console.log("Hello World!");
</Markdown>

// Advanced plugin configuration with options <Markdown remarkPlugins={[ remarkGfm, [remarkToc, { heading: "contents", maxDepth: 3 }], [remarkMath, { singleDollarTextMath: false }] ]} rehypePlugins={[ rehypeSlug, [rehypeHighlight, { detect: true, ignoreMissing: true, subset: ['javascript', 'typescript', 'css'] }], [rehypeKatex, { strict: false, trust: false, macros: { "\RR": "\mathbb{R}", "\NN": "\mathbb{N}" } }] ]} remarkRehypeOptions={{ allowDangerousHtml: true, clobberPrefix: 'user-content-', footnoteLabel: 'Footnotes', footnoteLabelTagName: 'h2' }}

Mathematical Document

Contents

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

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

// TypeScript code with syntax highlighting
interface User {
  id: number;
  name: string;
}
</Markdown>

// Custom plugin example function customRemarkPlugin() { return (tree, file) => { // Transform AST nodes visit(tree, 'text', (node) => { node.value = node.value.replace(/TODO:/g, 'πŸ“‹ TODO:'); }); }; }

<Markdown remarkPlugins={[ customRemarkPlugin, [remarkGfm, { singleTilde: false }] ]}

TODO: This will be prefixed with an emoji

This strikethrough requires double tildes </Markdown>

## External Type Dependencies

```typescript { .api }
// From 'hast' package
interface Element {
  type: 'element';
  tagName: string;
  properties: Properties;
  children: Array<Element | Text | Comment>;
  position?: Position;
}

interface Properties {
  [key: string]: boolean | number | string | null | undefined | Array<string | number>;
}

interface Text {
  type: 'text';
  value: string;
  position?: Position;
}

interface Comment {
  type: 'comment';
  value: string;
  position?: Position;
}

interface Parents {
  type: string;
  children: Array<Element | Text | Comment>;
}

interface Position {
  start: Point;
  end: Point;
}

interface Point {
  line: number;
  column: number;
  offset?: number;
}

// From 'unified' package
type PluggableList = Array<Pluggable>;
type Pluggable = Plugin | Preset | [Plugin, ...Parameters] | [Preset, ...Parameters];
type Plugin = (this: Processor, ...parameters: any[]) => Transformer | void;
type Preset = { plugins: PluggableList; settings?: Settings };
type Transformer = (tree: any, file: any, next?: Function) => any;
type Parameters = any[];
type Settings = Record<string, any>;

interface Processor {
  use(...args: any[]): Processor;
  parse(document: string | Buffer): any;
  run(tree: any, file?: any, callback?: Function): any;
  runSync(tree: any, file?: any): any;
}

// From 'remark-rehype' package
interface RemarkRehypeOptions {
  allowDangerousHtml?: boolean;
  passThrough?: Array<string>;
  handlers?: Record<string, Function>;
  unknownHandler?: Function;
  clobberPrefix?: string;
  footnoteLabel?: string;
  footnoteLabelTagName?: string;
  footnoteLabelProperties?: Properties;
  footnoteBackLabel?: string;
}

// From 'vfile' package
interface VFile {
  value: string | Buffer;
  path?: string;
  basename?: string;
  stem?: string;
  extname?: string;
  dirname?: string;
  history: Array<string>;
  messages: Array<VFileMessage>;
  data: Record<string, any>;
}

interface VFileMessage {
  reason: string;
  line?: number;
  column?: number;
  position?: Position;
  ruleId?: string;
  source?: string;
  fatal?: boolean;
}

Configuration Options

interface Options {
  /** 
   * Markdown content to render as a string
   * @default undefined
   */
  children?: string | null | undefined;
  
  /** 
   * Custom callback function to programmatically filter elements during processing
   * Called after allowedElements/disallowedElements filtering
   * @default undefined
   */
  allowElement?: AllowElement | null | undefined;
  
  /** 
   * Array of HTML tag names to allow in output (whitelist approach)
   * Cannot be used together with disallowedElements
   * @default undefined (allows all elements)
   */
  allowedElements?: ReadonlyArray<string> | null | undefined;
  
  /** 
   * Array of HTML tag names to remove from output (blacklist approach)
   * Cannot be used together with allowedElements
   * @default []
   */
  disallowedElements?: ReadonlyArray<string> | null | undefined;
  
  /** 
   * Object mapping HTML tag names to custom React components
   * @default undefined
   */
  components?: Components | null | undefined;
  
  /** 
   * Array of remark plugins to process markdown before converting to HTML
   * Plugins can be functions or [plugin, options] tuples
   * @default []
   */
  remarkPlugins?: PluggableList | null | undefined;
  
  /** 
   * Array of rehype plugins to process HTML after markdown conversion
   * Plugins can be functions or [plugin, options] tuples
   * @default []
   */
  rehypePlugins?: PluggableList | null | undefined;
  
  /** 
   * Configuration options passed to remark-rehype for markdown to HTML conversion
   * @default { allowDangerousHtml: true }
   */
  remarkRehypeOptions?: Readonly<RemarkRehypeOptions> | null | undefined;
  
  /** 
   * Whether to completely ignore HTML in markdown input
   * When true, HTML tags are treated as plain text
   * @default false
   */
  skipHtml?: boolean | null | undefined;
  
  /** 
   * Whether to extract children from disallowed elements instead of removing entirely
   * When false, disallowed elements and their children are removed
   * When true, only the element wrapper is removed, children are kept
   * @default false
   */
  unwrapDisallowed?: boolean | null | undefined;
  
  /** 
   * Function to transform/sanitize URLs in href, src, and other URL attributes
   * Receives the URL, attribute name, and containing element
   * @default defaultUrlTransform
   */
  urlTransform?: UrlTransform | null | undefined;
}

interface HooksOptions extends Options {
  /** 
   * React content to display while markdown is being processed asynchronously
   * Only used by MarkdownHooks component for client-side async processing
   * @default undefined
   */
  fallback?: ReactNode | null | undefined;
}

Security Features

React Markdown prioritizes security with multiple built-in protections:

  • No dangerouslySetInnerHTML: All content is processed through React's virtual DOM
  • URL Sanitization: Default defaultUrlTransform prevents XSS via malicious URLs
  • Element Filtering: Control exactly which HTML elements are allowed
  • Plugin Sandboxing: Plugins operate within the unified processing pipeline

Security Example:

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

// Highly secure configuration
<Markdown
  // Only allow safe elements
  allowedElements={[
    'p', 'strong', 'em', 'ul', 'ol', 'li', 
    'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
    'blockquote', 'code', 'pre'
  ]}
  
  // Skip any raw HTML
  skipHtml={true}
  
  // Use default URL sanitization
  urlTransform={defaultUrlTransform}
>
  {untrustedMarkdown}
</Markdown>

Error Handling

React Markdown handles various error conditions gracefully:

  • Invalid markdown: Renders as much as possible, invalid syntax is treated as text
  • Plugin errors: Async components (MarkdownHooks, MarkdownAsync) will throw errors
  • Type validation: Deprecated props trigger runtime errors with helpful messages
  • Configuration conflicts: Mutually exclusive options (e.g., allowedElements + disallowedElements) cause errors

Error Handling Example:

import React from "react";
import { MarkdownHooks } from "react-markdown";

function SafeMarkdown({ content }: { content: string }) {
  return (
    <ErrorBoundary fallback={<div>Failed to render markdown</div>}>
      <MarkdownHooks
        fallback={<div>Loading...</div>}
        remarkPlugins={[/* potentially failing async plugins */]}
      >
        {content}
      </MarkdownHooks>
    </ErrorBoundary>
  );
}