The fastest Markdown parser for React Native. Period.
react-native-nitro-markdown is a high-performance Markdown parser built on md4c (C++) and Nitro Modules. It parses complex Markdown, GFM, and LaTeX Math into a structured AST synchronously via JSI, bypassing the React Native Bridge entirely.
We benchmarked this library against the most popular JavaScript parsers on a real mobile device (iPhone 15 Pro, Release Mode) using a heavy 237KB Markdown document.
| Parser | Time (ms) | Speedup | Frame Drops (60fps) |
|---|---|---|---|
| ๐ Nitro Markdown (C++) | ~29 ms | 1x (Baseline) | ~1 frame (Smooth) |
| ๐ CommonMark (JS) | ~82 ms | 2.8x slower | ~5 frames (Jank) |
| ๐๏ธ Markdown-It (JS) | ~118 ms | 4.0x slower | ~7 frames (Jank) |
| ๐จ Marked (JS) | ~400 ms | 13.5x slower | ~24 frames (Freeze) |
Takeaway: JavaScript parsers trigger Garbage Collection pauses. Nitro uses C++ to parse efficiently with zero-copy overhead, keeping your UI thread responsive.
Choose your preferred package manager to install the package and its core dependency (react-native-nitro-modules).
npm
npm install react-native-nitro-markdown react-native-nitro-modulesNote: If you want to use Math (LaTeX) or certain Image features, you should also install the optional peer dependencies:
npm install react-native-svg react-native-mathjax-svg
Yarn
yarn add react-native-nitro-markdown react-native-nitro-modulesBun
bun add react-native-nitro-markdown react-native-nitro-modulespnpm
pnpm add react-native-nitro-markdown react-native-nitro-modulesStandard
cd ios && pod installIf you are using Expo, you must run a Prebuild (Development Build) because this package contains native C++ code.
npx expo install react-native-nitro-markdown react-native-nitro-modules
npx expo prebuildUse the Markdown component with built-in premium dark-mode styling:
import { Markdown } from "react-native-nitro-markdown";
export function MyComponent() {
return (
<Markdown options={{ gfm: true }}>
{"# Hello World\nThis is **bold** text."}
</Markdown>
);
}Override specific node types while keeping defaults for everything else:
import {
Markdown,
Heading,
type CustomRenderers,
} from "react-native-nitro-markdown";
import MathJax from "react-native-mathjax-svg";
const renderers: CustomRenderers = {
// Custom styled heading
heading: ({ node, children }) => (
<Heading level={node.level ?? 1}>
<Text style={{ color: "pink" }}>{children}</Text>
</Heading>
),
// Custom math renderer
math_inline: ({ node }) => <MathJax fontSize={16}>{node.content}</MathJax>,
math_block: ({ node }) => <MathJax fontSize={20}>{node.content}</MathJax>,
};
<Markdown renderers={renderers} options={{ gfm: true, math: true }}>
{markdown}
</Markdown>;You can easily customize the look and feel of the default components by passing a theme object. This allows you to match your app's brand without writing custom renderers for everything.
import { Markdown } from "react-native-nitro-markdown";
const myTheme = {
colors: {
text: "#2D3748",
heading: "#1A202C",
link: "#3182CE",
tableBorder: "#E2E8F0",
tableHeader: "#F7FAFC",
},
spacing: {
m: 16,
},
};
<Markdown theme={myTheme}>{"# Custom Branded Markdown"}</Markdown>;
> **Tip:** The default theme is optimized for Dark Mode. For Light Mode, pass a custom theme object or check out the [theme source](packages/react-native-nitro-markdown/src/theme.ts) to see all available tokens.For maximum control, data processing, or minimal JS overhead:
/**
* Only imports the parser.
* Zero UI overhead, purely synchronous AST generation.
*/
import { parseMarkdown } from "react-native-nitro-markdown/headless";
const ast = parseMarkdown("# Hello World");When streaming text token-by-token (e.g., from ChatGPT or Gemini), re-parsing the entire document in JavaScript for every token is too slow.
Nitro Markdown enables Native Streaming via JSI. The text buffer is maintained in C++ and updates are pushed directly to the native view, bypassing React completely.
import {
MarkdownStream,
useMarkdownSession,
} from "react-native-nitro-markdown";
export function AIResponseStream() {
// 1. Create a native session
const session = useMarkdownSession();
useEffect(() => {
// 2. Append chunks directly to C++ (Zero-Latency)
// Example: Socket.on('data', (chunk) => session.getSession().append(chunk));
session.getSession().append("Hello **Nitro**!");
return () => session.clear();
}, [session]);
// 3. Render the localized stream component
return (
<MarkdownStream session={session.getSession()} options={{ gfm: true }} />
);
}| Feature | Headless (/headless) |
Non-Headless (default) |
|---|---|---|
| Logic | Raw C++ md4c Parser | Parser + Full UI Renderer |
| Output | JSON AST Tree | React Native Views |
| Best For | Search Indexing, Custom UIs | Fast Implementation, Documentation |
| JS Overhead | ~4 KB | ~60 KB |
The parsing is synchronous and instant. It returns a fully typed JSON AST. We recommend using the /headless entry point if you only need the parser.
import { parseMarkdown } from "react-native-nitro-markdown/headless";
const ast = parseMarkdown(`
# Hello World
This is **bold** text and a [link](https://github.com).
`);
console.log(ast);
// Output: { type: "document", children: [...] }| Option | Type | Default | Description |
|---|---|---|---|
gfm |
boolean |
false |
Enable GitHub Flavored Markdown (Tables, Strikethrough, Autolinks, TaskLists). |
math |
boolean |
false |
Enable LaTeX Math support ($ and $$). |
Enable GitHub Flavored Markdown (Tables, TaskLists) or LaTeX Math support.
import { parseMarkdownWithOptions } from "react-native-nitro-markdown/headless";
const ast = parseMarkdownWithOptions(markdown, {
gfm: true, // Tables (supports complex nested content!), Strikethrough, Autolinks, TaskLists
math: true, // $E=mc^2$ and $$block$$
});The parser returns a MarkdownNode tree. The Types are fully exported for TypeScript support.
export interface MarkdownNode {
type: NodeType;
// Content for Text/Code/Math
content?: string;
// Hierarchy
children?: MarkdownNode[];
// Metadata
level?: number; // Headings (1-6)
href?: string; // Links
checked?: boolean; // Task Lists
language?: string; // Code Blocks
// Table Props
align?: "left" | "center" | "right";
isHeader?: boolean;
}
export type NodeType =
| "document"
| "paragraph"
| "text"
| "heading"
| "bold"
| "italic"
| "strikethrough"
| "link"
| "image"
| "code_inline"
| "code_block"
| "blockquote"
| "list"
| "list_item"
| "task_list_item"
| "table"
| "table_row"
| "table_cell"
| "math_inline"
| "math_block";We parse math delimiters ($ and $$) natively using the MD_FLAG_LATEXMATHSPANS flag in md4c.
To render the math, you should use a library like react-native-math-view, react-native-mathjax-svg, or react-native-katex inside your renderer:
// Inside your switch(node.type)
case 'math_inline':
return <MathView math={node.content} style={styles.math} />;
case 'math_block':
return <MathView math={node.content} style={styles.mathBlock} />;| Metric | Size |
|---|---|
| Packed (tarball) | ~75 kB |
| Unpacked | ~325 kB |
| Total files | 55 |
The package includes the md4c C source code (~244 kB) which is compiled natively on iOS and Android. This is a one-time cost that enables the high-performance parsing.
See the contributing guide to learn how to contribute to the repository and the development workflow.
MIT
Built with โค๏ธ using Nitro Modules and md4c.

