npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

react-native-markdown-x

v0.2.5

Published

TypeScript-first React Native markdown renderer with CommonMark compliance, native component rendering, and enhanced default features.

Downloads

29

Readme

React Native Markdown X npm version Known Vulnerabilities

THE definitive CommonMark renderer for React Native. Built with TypeScript, using native components, fully customizable.

Features

  • TypeScript First: Complete type safety with exported types for customization
  • Native Components: No WebView - renders using React Native components
  • Fully Customizable: Override any render function or style
  • Better Architecture: Clean AstRenderer class with proper fallbacks
  • Modern Performance: Optimized for React Native applications

Install

npm install react-native-markdown-x
yarn add react-native-markdown-x
pnpm add react-native-markdown-x
bun add react-native-markdown-x

Get Started

import React from 'react';
import { ScrollView } from 'react-native';
import { Markdown } from 'react-native-markdown-x';

const App = () => (
  <ScrollView style={{ padding: 16 }}>
    <Markdown>
      {`# Hello World\n\n**Bold text** and *italic text*\n\nNormal text here.`}
    </Markdown>
  </ScrollView>
);

API Reference

Core Props

| Prop | Type | Default | Description | | --- | --- | --- | --- | | children | string \| AstNode[] | required | Markdown content or pre-processed AST | | styles | StyleMap | Default styles | Style overrides for markdown elements | | renderFunctions | RenderFunctionMap | Default renders | Custom render functions | | onLinkPress | (url: string) => void | Linking.openURL | Custom link handling |

Advanced Props

| Prop | Type | Default | Description | | --- | --- | --- | --- | | renderer | AstRenderer | instanceOf(AstRenderer) | Custom renderer instance (mutually exclusive with renderFunctions/styles) | | useDefaultRenderFunctions | boolean | true | Whether to merge with default renders or use only provided functions | | useDefaultStyles | boolean | true | Whether to merge with default styles or use only provided styles | | markdownit | MarkdownIt | instanceOf(MarkdownIt) | Custom markdown-it configuration | | textComponent | ComponentType<TextProps> | Text | Custom Text component for rendering | | maxTopLevelChildren | number | undefined | Limit rendered top-level elements | | topLevelExceededComponent | JSX.Element | <Text key="dotdotdot">...</Text> | Component shown when maxTopLevelChildren is exceeded | | allowedImageHandlers | string[] | ['data:image/png;base64', 'data:image/gif;base64', 'data:image/jpeg;base64', 'https://', 'http://'] | Allowed image URL prefixes | | defaultImageHandler | string | https:// | Prepended to image URLs not matching allowedImageHandlers | | debugPrintTree | boolean | false | Log AST structure for debugging |

Syntax Support

Core Markdown Elements

All standard CommonMark elements are supported with native React Native rendering:

  • Headings: # h1 through ###### h6
  • Emphasis: **bold**, __bold__, *italic*, _italic_, ~~strikethrough~~
  • Lists: Ordered (1. item) and unordered (- item) with nesting support
  • Links: [text](url) and [text](url "title") with auto-linking
  • Images: ![alt](src) and reference-style ![alt][id]
  • Code: Inline `code` and fenced blocks with syntax highlighting
  • Tables: Full table support with alignment options
  • Blockquotes: Single and nested > quote blocks
  • Horizontal Rules: --- or ___ separators
  • Typographic Replacements: Smart quotes, em-dashes, and symbols

| Feature | iOS | Android | |---------|-----|---------| | Headings | | | | Emphasis | | | | Lists | | | | Links | | | | Images | | | | Code | | | | Tables | | | | Blockquotes | | | | Horizontal Rules | | | | Typographic Replacements | | |

This is all of the markdown in one place for testing that your applied styles work in all cases

Headings

  # h1 Heading 8-)
  ## h2 Heading
  ### h3 Heading
  #### h4 Heading
  ##### h5 Heading
  ###### h6 Heading


Horizontal Rules

  Some text above
  ___

  Some text in the middle

  ---

  Some text below


Emphasis

  **This is bold text**

  __This is bold text__

  *This is italic text*

  _This is italic text_

  ~~Strikethrough~~


Blockquotes

  > Blockquotes can also be nested...
  >> ...by using additional greater-than signs right next to each other...
  > > > ...or with spaces between arrows.


Lists

  Unordered

  + Create a list by starting a line with `+`, `-`, or `*`
  + Sub-lists are made by indenting 2 spaces:
    - Marker character change forces new list start:
      * Ac tristique libero volutpat at
      + Facilisis in pretium nisl aliquet. This is a very long list item that will surely wrap onto the next line.
      - Nulla volutpat aliquam velit
  + Very easy!

  Ordered

  1. Lorem ipsum dolor sit amet
  2. Consectetur adipiscing elit. This is a very long list item that will surely wrap onto the next line.
  3. Integer molestie lorem at massa

  Start numbering with offset:

  57. foo
  58. bar


Code

  Inline \`code\`

  Indented code

      // Some comments
      line 1 of code
      line 2 of code
      line 3 of code


  Block code "fences"

  \`\`\`
  Sample text here...
  \`\`\`

  Syntax highlighting

  \`\`\` js
  var foo = function (bar) {
    return bar++;
  };

  console.log(foo(5));
  \`\`\`


Tables

  | Option | Description |
  | ------ | ----------- |
  | data   | path to data files to supply the data that will be passed into templates. |
  | engine | engine to be used for processing templates. Handlebars is the default. |
  | ext    | extension to be used for dest files. |

  Right aligned columns

  | Option | Description |
  | ------:| -----------:|
  | data   | path to data files to supply the data that will be passed into templates. |
  | engine | engine to be used for processing templates. Handlebars is the default. |
  | __Headings__ <br/> <pre>
  | ext    | extension to be used for dest files. |


Links

  [link text](https://www.google.com)

  [link with title](https://www.google.com "title text!")

  Autoconverted link https://www.google.com (enable linkify to see)

Images

  ![Minion](https://octodex.github.com/images/minion.png)
  ![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")

  Like links, Images also have a footnote style syntax

  ![Alt text][id]

  With a reference later in the document defining the URL location:

  [id]: https://octodex.github.com/images/dojocat.jpg  "The Dojocat"


Typographic Replacements

  Enable typographer option to see result.

  (c) (C) (r) (R) (tm) (TM) (p) (P) +-

  test.. test... test..... test?..... test!....

  !!!!!! ???? ,,  -- ---

  "Smartypants, double quotes" and 'single quotes'

Customization

Styling

Customize appearance using the styles prop. By default, your styles are merged with the built-in defaults - changes to specific elements override defaults for those elements only.

// Merged with defaults (useDefaultStyles: true - default behavior)
<Markdown
  styles={{
    body: { color: 'red', fontSize: 14 },     // Affects all text
    heading1: { color: 'purple' },           // Overrides default h1 style
    code_block: { backgroundColor: '#f0f0f0' }
  }}
>
  {content}
</Markdown>

// Complete style override (useDefaultStyles: false)
<Markdown
  styles={{
    body: { color: 'red', fontSize: 14 },
    heading1: { fontSize: 24, fontWeight: 'bold' }
    // Only these styles will be applied - no defaults
  }}
  useDefaultStyles={false}
>
  {content}
</Markdown>

Note: The text style doesn't apply to all text (e.g., list markers). Use body for global text styling.

Render Functions

Override how elements are rendered using the renderFunctions prop. Like styles, these are merged with defaults by default:

// Merged with defaults (useDefaultRenderFunctions: true - default behavior)
const customRenders = {
  heading1: (node, children, parent, styles) => (
    <Text key={node.key} style={[styles.heading1, { borderLeftWidth: 4, borderLeftColor: 'blue' }]}>
      📝 {children}
    </Text>
  ),
  strong: (node, children, parent, styles) => (
    <Text key={node.key} style={[styles.strong, { color: 'red' }]}>
      {children}
    </Text>
  )
};

<Markdown renderFunctions={customRenders}>{content}</Markdown>

// Complete override (useDefaultRenderFunctions: false)
const minimalRenders = {
  body: ({ children }) => <View>{children}</View>,
  heading1: ({ node, children, styles }) => (
    <Text key={node.key} style={styles.heading1}>{children}</Text>
  ),
  // Must provide all render functions you need - no defaults
};

<Markdown 
  renderFunctions={minimalRenders}
  useDefaultRenderFunctions={false}
>
  {content}
</Markdown>

⚠️ Important: When providing both custom styles and renderFunctions for the same element type, you must ensure your custom render function actually uses the provided style from the styles parameter. The style won't be applied automatically!

// ❌ Style will be ignored - custom render function doesn't use it
<Markdown
  styles={{ heading1: { color: 'red', fontSize: 24 } }}
  renderFunctions={{
    heading1: ({ node, children }) => <Text key={node.key}>{children}</Text>
  }}
>
  {content}
</Markdown>

// ✅ Style will be applied - custom render function uses styles parameter
<Markdown
  styles={{ heading1: { color: 'red', fontSize: 24 } }}
  renderFunctions={{
    heading1: ({ node, children, styles }) => (
      <Text key={node.key} style={styles.heading1}>{children}</Text>
    )
  }}
>
  {content}
</Markdown>

// ✅ Alternative - apply styles directly in the render function
<Markdown
  renderFunctions={{
    heading1: ({ node, children }) => (
      <Text key={node.key} style={{ color: 'red', fontSize: 24 }}>
        {children}
      </Text>
    )
  }}
>
  {content}
</Markdown>

Available Elements

| Element | Render Function | Style Key(s) | |---------|----------------|--------------| | Body | body | body | | Headings | heading1-heading6 | heading1-heading6 | | Text formatting | strong, em, s | strong, em, s | | Lists | bullet_list, ordered_list, list_item | bullet_list, ordered_list, list_item, list_item_*_marker, list_item_*_content | | Code | code_inline, code_block, fence | code_inline, code_block, fence | | Tables | table, thead, tbody, th, tr, td | table, thead, tbody, th, tr, td | | Links & Images | link, blocklink, image | link, blocklink, image | | Other | blockquote, hr, paragraph | blockquote, hr, paragraph |

Extensions & Plugins

Extend functionality using any markdown-it compatible plugin. Use debugPrintTree to identify new components and create corresponding render functions:

import MarkdownIt from 'markdown-it';
import blockEmbedPlugin from 'markdown-it-block-embed';

const markdownItInstance = MarkdownIt({typographer: true})
  .use(blockEmbedPlugin, { containerClassName: "video-embed" });

const customRenders = {
  video: (node, children, parent, styles) => {
    // Access plugin data through node.sourceInfo
    const { videoID, serviceName } = node.sourceInfo;
    return (
      <Text key={node.key} style={styles.video}>
        🎥 {serviceName} video: {videoID}
      </Text>
    );
  }
};

<Markdown 
  markdownit={markdownItInstance}
  renderFunctions={customRenders}
  styles={{ video: { color: 'blue' } }}
>
  {`@[youtube](lJIrF4YjHfQ)`}
</Markdown>

This example shows full integration with a video embed plugin:

import React from 'react';
import { ScrollView, Text } from 'react-native';
import { Markdown } from 'react-native-markdown-x';
import MarkdownIt from 'markdown-it';
import blockEmbedPlugin from 'markdown-it-block-embed';

const markdownItInstance = MarkdownIt({typographer: true})
  .use(blockEmbedPlugin, { containerClassName: "video-embed" });

const renderFunctions = {
  video: (node, children, parent, styles) => {
    console.log(node); // Debug: see available properties
    return (
      <Text key={node.key} style={styles.video}>
        🎥 Video Component Here
      </Text>
    );
  }
};

const styles = { video: { color: 'red', fontSize: 16 } };

const content = `# Video Example\n\n@[youtube](lJIrF4YjHfQ)`;

export default () => (
  <ScrollView style={{ padding: 16 }}>
    <Markdown
      debugPrintTree
      markdownit={markdownItInstance}
      renderFunctions={renderFunctions}
      styles={styles}
    >
      {content}
    </Markdown>
  </ScrollView>
);

The debugPrintTree output shows the AST structure:

body
-heading1
--textgroup
---text
-video

And node properties include all plugin data:

{
  type: "video",
  sourceInfo: {
    service: "youtube",
    videoID: "lJIrF4YjHfQ",
    options: { width: 640, height: 390 }
  }
}
import { Markdown } from 'react-native-markdown-x';

const styles = {
  body: { fontSize: 16, lineHeight: 24 },
  heading1: { fontSize: 32, fontWeight: 'bold', marginBottom: 16 },
  heading2: { fontSize: 24, fontWeight: 'bold', marginBottom: 12 },
  strong: { fontWeight: 'bold', color: '#333' },
  em: { fontStyle: 'italic', color: '#666' },
  code_inline: { 
    backgroundColor: '#f5f5f5', 
    paddingHorizontal: 4,
    fontFamily: 'monospace'
  },
  blockquote: {
    borderLeftWidth: 4,
    borderLeftColor: '#ddd',
    paddingLeft: 16,
    fontStyle: 'italic'
  }
};

<Markdown styles={styles}>{content}</Markdown>

Advanced Usage

Custom Link Handling

Option 1: onLinkPress callback

const onLinkPress = (url) => {
  if (url.includes('internal://')) {
    // Handle internal navigation
    navigate(url);
    return false; // Don't use default handler
  }
  return true; // Use default Linking.openURL
};

<Markdown onLinkPress={onLinkPress}>{content}</Markdown>

Option 2: Custom render function

const renderFunctions = {
  link: (node, children, parent, styles) => (
    <Text key={node.key} style={styles.link} onPress={() => handleCustomLink(node.attributes.href)}>
      {children}
    </Text>
  )
};


<Markdown renderFunctions={renderFunctions}>{content}</Markdown>

Disabling Markdown Features

Disable specific markdown types for mobile-friendly content:

import MarkdownIt from 'markdown-it';

// Disable links and images
const restrictedMarkdown = MarkdownIt({typographer: true}).disable(['link', 'image']);

<Markdown markdownit={restrictedMarkdown}>
  {`# This heading works\n[but this link](is plain text)`}
</Markdown>

Pre-processing Content

For advanced use cases, process the AST directly:

import { Markdown, tokensToAST, stringToTokens } from 'react-native-markdown-x';
import MarkdownIt from 'markdown-it';

const markdownItInstance = MarkdownIt({typographer: true});
const ast = tokensToAST(stringToTokens(content, markdownItInstance));

// Modify AST as needed
<Markdown>{ast}</Markdown>

Custom AstRenderer

For complete control, create a custom renderer. When using a custom renderer, renderFunctions, styles, useDefaultRenderFunctions, and useDefaultStyles props are ignored:

import { Markdown, AstRenderer, DEFAULT_RENDER_FUNCTIONS, DEFAULT_STYLES } from 'react-native-markdown-x';

const customRenderer = new AstRenderer({
  renderFunctions: {
    ...DEFAULT_RENDER_FUNCTIONS,
    heading1: ({ node, children, styles }) => (
      <Text key={node.key} style={[styles.heading1, { color: 'blue' }]}>
        {children}
      </Text>
    )
  },
  styles: DEFAULT_STYLES,
  useDefaultStyles: false,
  onLinkPress: (url) => console.log('Link pressed:', url)
});

<Markdown renderer={customRenderer}>{content}</Markdown>

Architecture & Type Safety

Prop Conflicts Prevention

The library uses TypeScript discriminated unions to prevent conflicting prop combinations:

// ✅ Valid: Using individual props
<Markdown styles={myStyles} renderFunctions={myRenders}>
  {content}
</Markdown>

// ✅ Valid: Using custom renderer
<Markdown renderer={customRenderer}>
  {content}
</Markdown>

// ❌ TypeScript Error: Cannot use both renderer and individual props
<Markdown 
  renderer={customRenderer}
  styles={myStyles}        // TypeScript prevents this
  renderFunctions={myRenders}  // TypeScript prevents this
>
  {content}
</Markdown>

Performance Optimizations

  • Memoized Renderer: The AstRenderer instance is memoized to prevent unnecessary re-creation
  • Memoized Parser: The MarkdownIt instance is memoized for consistent parsing
  • Style Flattening: Styles are flattened once during renderer creation for optimal performance
  • Efficient Re-renders: Only re-renders when props actually change

Advanced Configuration

import { Markdown, AstRenderer } from 'react-native-markdown-x';
import { MyCustomText } from './components';

// Complete customization with performance optimization
const renderer = new AstRenderer({
  textComponent: MyCustomText,
  useDefaultRenderFunctions: false,  // Start from scratch
  useDefaultStyles: false,           // Complete style control
  renderFunctions: {
    // Only define what you need
    body: ({ children }) => <ScrollView>{children}</ScrollView>,
    paragraph: ({ children, styles }) => <Text style={styles.paragraph}>{children}</Text>
  },
  styles: {
    paragraph: { marginBottom: 16 }
  }
});

<Markdown renderer={renderer}>{content}</Markdown>

Migration Guide

Migrating from other React Native markdown libraries:

| Other Libraries | React Native Markdown X | |----------------|-------------------------| | style prop | styles prop | | mergeStyle prop | useDefaultStyles prop | | rules prop | renderFunctions prop |

// Before (other libraries)
<Markdown style={myStyles} mergeStyle={true} rules={myRules}>
  {content}
</Markdown>

// After (React Native Markdown X)
<Markdown styles={myStyles} useDefaultStyles={true} renderFunctions={myRenderFunctions}>
  {content}
</Markdown>

This library represents the next evolution in React Native markdown rendering, combining the best features from existing libraries with modern TypeScript architecture and improved performance.