THE definitive CommonMark renderer for React Native. Built with TypeScript, using native components, fully customizable.
- 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
- Standardized API: All render functions use consistent props interface
- Modern Performance: Optimized for React Native applications
npm install react-native-markdown-xyarn add react-native-markdown-xpnpm add react-native-markdown-xbun add react-native-markdown-ximport 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>
);| 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 |
| 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 |
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
View Live Examples
| Feature | iOS | Android |
|---|---|---|
| Headings | ![]() |
![]() |
| Emphasis | ![]() |
![]() |
| Lists | ![]() |
![]() |
| Links | ![]() |
![]() |
| Images | ![]() |
![]() |
| Code | ![]() |
![]() |
| Tables | ![]() |
![]() |
| Blockquotes | ![]() |
![]() |
| Horizontal Rules | ![]() |
![]() |
| Typographic Replacements | ![]() |
![]() |
All Markdown for Testing
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'
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.
Override how elements are rendered using the renderFunctions prop. All render functions have a standardized interface with consistent props and return types. Like styles, these are merged with defaults by default:
// Merged with defaults (useDefaultRenderFunctions: true - default behavior)
const customRenders = {
heading1: ({ node, children, styles, Text }) => (
<Text key={node.key} style={[styles.heading1, { borderLeftWidth: 4, borderLeftColor: 'blue' }]}>
📝 {children}
</Text>
),
strong: ({ node, children, styles, Text }) => (
<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 }) => (
<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>All render functions receive the same props object with the following signature:
type RenderFunction = (props: {
node: AstNode; // The current AST node
children?: ReactNode; // Rendered child elements
parents: AstNode[]; // Parent nodes in the AST tree
styles: FlattenedStyleMap; // All computed styles
Text: ComponentType<TextProps>; // Text component (respects textComponent prop)
inheritedStyles?: TextOnlyStyle; // Inherited text styles from parents
onLinkPress?: (url: string) => void; // Link handler function
allowedImageHandlers?: string[]; // Allowed image URL prefixes
defaultImageHandler?: string; // Default image URL prefix
}) => ReactNode;This standardized interface means:
- ✅ Consistent API: No more different function signatures per element type
- ✅ Text Component Access: The
Textcomponent is always available as a prop - ✅ Type Safety: Full TypeScript support with proper prop types
- ✅ Future Proof: New features can be added without breaking changes
⚠️ Important: When providing both customstylesandrenderFunctionsfor 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 }) => <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 }) => ( <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 }) => ( <Text key={node.key} style={{ color: 'red', fontSize: 24 }}> {children} </Text> ) }} > {content} </Markdown>
| 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 |
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, styles, Text }) => {
// 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>Complete Plugin Integration Example
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, styles, Text }) => {
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 }
}
}Complete Styling Example
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>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>const renderFunctions = {
link: ({ node, children, styles, Text, onLinkPress }) => (
<Text
key={node.key}
style={styles.link}
onPress={() => handleCustomLink(node.attributes.href)}
>
{children}
</Text>
)
};
<Markdown renderFunctions={renderFunctions}>{content}</Markdown>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>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>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 }) => (
<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>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>- 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
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 }) => <Text style={styles.paragraph}>{children}</Text>
},
styles: {
paragraph: { marginBottom: 16 }
}
});
<Markdown renderer={renderer}>{content}</Markdown>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.



















