# react-native-live-markdown: a Markdown-aware TextInput for React Native

> The Expensify react-native-live-markdown library swaps React Native's TextInput for a component that formats Markdown as you type while keeping the native selection, spellcheck and autocomplete behaviour. It suits a chat composer that needs rich formatting, and it costs you a native rebuild, the New Architecture and a worklet-based parser you must supply.

**Expensify/react-native-live-markdown** — Drop-in replacement for React Native's TextInput component with Markdown formatting.

- Repository: https://github.com/Expensify/react-native-live-markdown
- Website: https://www.npmjs.com/package/@expensify/react-native-live-markdown
- Stars: 1,332 · Forks: 116
- Language: TypeScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/expensify-react-native-live-markdown

## Formatting on the UI thread, with the native field intact

The library is a drop-in replacement for the `<TextInput>` component that applies Markdown formatting live and synchronously on every keystroke, while staying fully native in the ways that matter: selection, spellcheck and autocomplete all behave as they do in a regular TextInput. That combination is the whole problem this solves. The usual approach to rich text in a mobile composer is to render a preview below the field, or to swap in a native editor that gives up the platform's own text behaviours. Here the field is still a real text field, and the formatting is a layer over the text. The primary language is TypeScript, the licence is MIT, the project is not archived, and the last push was on 2026-10-06 with no GitHub releases published, the npm package version being 0.1.343.

## The parser is a worklet, and that is the whole contract

Customisation happens through the `parser` prop, which is a function taking a plaintext string and returning an array of `MarkdownRange` objects, where each range has a `type`, a `start`, a `length` and an optional `depth`. The parser has to be marked as a worklet, with the `'worklet'` directive at the top of the function, because it is executed on the UI thread as the user types. The types it can return are fixed and enumerated: `bold`, `italic`, `strikethrough`, `emoji`, `mention-here`, `mention-user`, `mention-report`, `link`, `code`, `pre`, `blockquote`, `h1` and `syntax`. The `syntax` type is the one to notice, because it is how delimiters are marked separately from their content, which is what lets a parser colour the asterisks of a bold span differently from the text inside it. The example in the documentation makes the pattern clear, using a regular expression to find text between two asterisks and pushing three ranges per match, one for the opening delimiter, one for the content and one for the closing delimiter.

## The built-in parser has a character limit, deliberately

The built-in `parseExpensiMark` parser accepts an optional maximum input length. It defaults to 4,000 characters and returns no formatting ranges when the input is longer than the configured limit. That is a performance decision rather than a bug, since the parser runs on the UI thread on every keystroke and a very long document would make typing lag. The escape hatch is to wrap the parser in a worklet that calls it with a different limit:

```ts
function parser(input: string) {
  'worklet';

  return parseExpensiMark(input, 5000);
}
```

Both the documentation tips that follow the styling section and the parsing section make the same recommendation, to store the parser function and the style object outside a component body or memoize them with `React.useMemo`. That advice exists for the same reason. A new function identity on every render invalidates the worklet and the memoized style, and on the UI thread that cost is paid per keystroke rather than per frame.

## Styling the tokens, not the field

The `MarkdownTextInput` takes a `style` prop like a regular TextInput, and separately a `markdownStyle` object that controls how each token type is rendered. The style object supports all the colour representations React Native supports, including `PlatformColor` and `DynamicColorIOS`. The set of customisable styles is currently limited, and the documentation flags that as subject to change. The example covers `syntax`, `link`, `h1`, `emoji`, `blockquote`, `code`, `pre`, `mentionHere` and `mentionUser`, and the shape is per-type with keys like `color`, `fontSize`, `fontFamily`, `backgroundColor`, and for blockquote the border and padding properties. The same object can be passed to several components through the `markdownStyle` prop, which is the reason to define it once at module level.

## Installation carries three pinned dependencies and a native rebuild

The install is one command with four packages, and the versions matter:

```sh
yarn add @expensify/react-native-live-markdown react-native-worklets expensify-common html-entities@2.5.3
npm install @expensify/react-native-live-markdown react-native-worklets expensify-common html-entities@2.5.3 --save
npx expo install @expensify/react-native-live-markdown react-native-worklets expensify-common html-entities@2.5.3
```

The requirements are `react-native-worklets` 0.7.0 or newer, `expensify-common` 2.0.207 or newer, and `html-entities` at exactly 2.5.3 if you use the default built-in ExpensiMark parser. The pin on html-entities is exact rather than a minimum, which is unusual and worth respecting. On iOS the native dependencies are installed with CocoaPods:

```sh
cd ios && bundler install && bundler exec pod install
```

The library includes native code, so the native app has to be rebuilt, and the documentation asks you to follow the react-native-worklets getting started guide to avoid issues. Two restrictions are called out. The library supports only the New Architecture, and it does not support Expo Go, so you need to set up an Expo Dev Client and go through prebuild.

## Only ExpensiMark ships in the box

The markdown flavour support is narrow, and it is the clearest limit on the library. Currently, react-native-live-markdown supports only the ExpensiMark flavour out of the box, which is Expensify's own parser living in the expensify-common package. Anything else means writing a custom parser worklet and passing it through the `parser` prop. That is a reasonable trade rather than a limitation of ambition, because the parser interface is small and the range model is generic, so a CommonMark implementation is a mapping problem rather than an architectural one. But it does mean the library is not a markdown renderer for React Native in the general sense. It is a formatting engine with one flavour of rules attached to it and an extension point for others, and the cost of that design is that the rules you get are not the ones most people expect from the word markdown.

## What is in the repository

The tree shows a library with native code on both platforms and a fair amount of tooling. There is an `apple/` directory alongside `android/`, a `cpp/` directory for the shared native implementation, a `RNLiveMarkdown.podspec` at the root, and a `patches/` directory plus a `prepare` script running `patch-package && bob build`, which is the standard react-native-builder-bob pipeline for emitting the commonjs and module builds named in the package manifest as `main` and `module`. The `files` array is careful to ship the native sources and to exclude build directories and test files. There is a `mock/` directory shipped in the package, an `example/` app with its own Gemfile and Metro config, and a `WebExample/` directory, which lines up with the claim of universal support across Android, iOS and web. The scripts include `jest` for tests, `tsc --noEmit` for typecheck, ESLint with separate root, example and WebExample targets, `lefthook.yml` for git hooks, and `release-it` for publishing.

## Conclusion

Adopt react-native-live-markdown if you need a chat or comment composer that shows Markdown formatting while the user types and you are on the New Architecture with a native build. Skip it if you need CommonMark, since only the ExpensiMark flavour ships out of the box and everything else means writing your own parser worklet, and skip it if you cannot rebuild the native app or use Expo Go, both of which the documentation rules out. Before you start, install the pinned dependencies, since react-native-worklets 0.7.0 or newer, expensify-common 2.0.207 or newer and html-entities at exactly 2.5.3 are required for the built-in parser, then plan for the CocoaPods step on iOS.

## FAQ

### How do I install react-native-live-markdown?

Install it with the package manager of your choice, for example `yarn add @expensify/react-native-live-markdown react-native-worklets expensify-common html-entities@2.5.3`. It requires react-native-worklets 0.7.0 or newer and expensify-common 2.0.207 or newer, plus html-entities at exactly 2.5.3 for the built-in parser.

### Does react-native-live-markdown work with Expo Go?

No. The library does not support Expo Go, so you need to set up an Expo Dev Client and go through prebuild. It also supports only the New Architecture, and since it includes native code you have to rebuild the native app, including the iOS CocoaPods step.

### How do I write a custom parser for react-native-live-markdown?

Pass a function through the parser prop that takes a plaintext string and returns an array of MarkdownRange objects with a type, start and length. The function must be marked as a worklet with the 'worklet' directive, because it runs on the UI thread as the user types.

### What is the maximum input length of the built-in ExpensiMark parser?

It defaults to 4,000 characters, and it returns no formatting ranges when the input is longer than the limit. To change it, wrap parseExpensiMark in a worklet that passes a different limit, for example parseExpensiMark(input, 5000).

### Which markdown flavours does react-native-live-markdown support?

Only ExpensiMark out of the box. Other behaviour is possible by passing a custom parser worklet function through the parser prop.

## Sources

- [Expensify/react-native-live-markdown on GitHub](https://github.com/Expensify/react-native-live-markdown)
- [Issues](https://github.com/Expensify/react-native-live-markdown/issues)
- [License: MIT](https://github.com/Expensify/react-native-live-markdown/blob/main/LICENSE)
- [Project website](https://www.npmjs.com/package/@expensify/react-native-live-markdown)
- [README](https://github.com/Expensify/react-native-live-markdown/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/expensify-react-native-live-markdown
