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 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-streaming-markdown

v0.1.1

Published

Markdown renderer for React Native built for streaming AI responses. Renders partial markdown without flicker, handles unclosed code fences and emphasis, and keeps node identity stable across frames. Zero dependencies.

Readme

react-native-streaming-markdown

npm version license types

Markdown for React Native that is meant to be rendered while it is still arriving.

Existing markdown renderers assume you hold the whole document. An AI response does not work that way — it arrives a few characters at a time, and every frame you render is a prefix of a document that is not finished yet. Rendering those prefixes naively makes text flash bold and then un-bold, code blocks appear as paragraphs and then restyle, and React remount every node on every token.

This library parses partial markdown on purpose. Zero dependencies.

npm install react-native-streaming-markdown

Usage

import { Markdown } from 'react-native-streaming-markdown';

function Message({ text, isStreaming }) {
  return (
    <Markdown streaming={isStreaming} cursor>
      {text}
    </Markdown>
  );
}

Pass the partial string straight through as it grows. That is the whole API for the common case.

The problem, concretely

Say the model is emitting Use **bold** here. Here is what each renderer draws as the characters land:

| source so far | a regex renderer | this library | | --------------- | ---------------- | ------------ | | Use | plain | plain | | Use ** | bold | plain | | Use **bo | bold | plain | | Use **bold | bold | plain | | Use **bold** | bold | bold |

The regex renderer commits to bold the moment it sees **, before it knows the span ever closes. If the model was actually writing 2 ** 8, it has to undo it. That flash is the flicker.

The rule here is the opposite: an opening delimiter with no closer renders as literal text. A span turns bold once, in one direction, and never turns back.

Block-level constructs follow the inverse rule, for the same reason. An unclosed ``` fence renders as a code block immediately — a fence is always going to become a code block, so holding it back as a paragraph would mean restyling the entire snippet when the closing fence lands.

You can watch both rules in the terminal:

npm run build && npm run demo

Why it does not remount

Every node's key comes from the line it starts on in the source, not from its index in an array. Append a paragraph to a document and the blocks above it keep the keys they had, so React updates them in place instead of tearing them down. Key by array index instead and inserting one block shifts every key below it, remounting the whole document on a single new line.

parse('# Title\n\nFirst.').blocks.map(b => b.key)          // ['b0', 'b2']
parse('# Title\n\nFirst.\n\nSecond.').blocks.map(b => b.key) // ['b0', 'b2', 'b4']

Props

| prop | type | default | | | --- | --- | --- | --- | | children | string | — | the markdown source, partial or complete | | streaming | boolean | true | set false once the full document has arrived | | cursor | boolean | false | show a caret at the end while text is still arriving | | styles | MarkdownStyles | — | override any style key; the rest are kept | | onLinkPress | (href: string) => void | Linking.openURL | route links in-app | | renderers | Partial<Record<BlockNode['type'], fn>> | — | replace the rendering of a block type |

Styling

Override only what you want. Everything you leave out keeps its default.

<Markdown
  styles={{
    text: { fontSize: 15, color: '#E6E6E6' },
    codeBlock: { backgroundColor: '#16181D' },
    link: { color: '#7AA2F7' },
  }}
>
  {text}
</Markdown>

Custom block rendering

Useful for a code block that needs a copy button or syntax highlighting:

<Markdown
  renderers={{
    codeBlock: (node) => <HighlightedCode lang={node.lang} value={node.value} />,
  }}
>
  {text}
</Markdown>

Using the parser on its own

The parser has no React Native dependency, so it runs in tests, on a server, or on the web. Import it from the /parser subpath to avoid pulling in the component:

import { parse } from 'react-native-streaming-markdown/parser';

const { blocks, pending } = parse('```js\nconst a = 1;');
// blocks[0].type === 'codeBlock', blocks[0].open === true
// pending === 'codeBlock'

pending tells you the document ended mid-construct — 'codeBlock', 'emphasis', 'link', 'heading' or 'listItem' — which is how the component decides where to put the caret.

Supported syntax

Headings (1–6), paragraphs with soft wrapping, bold, italic, strikethrough, inline code, links, hard breaks, backslash escapes, bullet and ordered lists with nesting, blockquotes, fenced code blocks with a language tag, and horizontal rules.

Deliberately not supported: HTML passthrough, tables, reference links, footnotes. They are rare in AI output and each one is a large surface to keep stable across partial input.

snake_case identifiers are left alone rather than italicised, which matters when the model is writing code in prose.

Compatibility

React Native 0.68+ and React 17+. Works on Expo. No native modules, so there is nothing to link and it runs in Expo Go.

How it works

docs/DESIGN.md sets out the four failures a normal markdown renderer has on streaming input — flicker, restyle, remount and crash — and the rule each one is solved by.

Contributing

Issues and pull requests are welcome — see CONTRIBUTING.md.

npm install
npm test
npm run typecheck

Author

Syed Arbab Ali Shah — @ARBAB1

License

Copyright (c) 2026 Syed Arbab Ali Shah.

Released under the MIT License. You may use it commercially, modify it and redistribute it; the copyright notice has to travel with it.