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
Maintainers
Readme
React Native Markdown X

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-xyarn add react-native-markdown-xpnpm add react-native-markdown-xbun add react-native-markdown-xGet 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:
# h1through###### 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:
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
> quoteblocks - 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


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
stylesandrenderFunctionsfor the same element type, you must ensure your custom render function actually uses the provided style from thestylesparameter. 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
-videoAnd 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
AstRendererinstance is memoized to prevent unnecessary re-creation - Memoized Parser: The
MarkdownItinstance 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.
