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-markdown-stream

v0.1.4

Published

A lightweight React Native library for rendering live-updating Markdown content in real time

Readme

react-native-markdown-stream

A lightweight React Native renderer that turns streaming Markdown (chat responses, AI completions, docs) into polished UI in real time.

Highlights

  • Streaming-first: AsyncIterable, ReadableStream, generators, or manual chunk pushes all work out of the box.
  • Reveal controls: Animate output per chunk, word, or character with configurable delays.
  • Theming without repainting the screen: ship light/dark presets, or merge your own colors with a simple config object.
  • Code & math aware: inline/code blocks with optional copy actions, math rendering via the optional react-native-math-view peer.
  • Zero native code: works in the classic architecture and the New Architecture (Fabric/TurboModules) without extra steps.

Installation

# yarn
yarn add react-native-markdown-stream

# npm
npm install react-native-markdown-stream

Optional math support

Install the optional peer if you want LaTeX blocks to render with react-native-math-view:

yarn add react-native-math-view

Without it installed, math blocks gracefully fall back to styled text.

Metro configuration

The library bundles modern ESM packages from the remark/unified ecosystem. Metro 0.72+ understands them as long as package exports are enabled.

Add or update metro.config.js in your app:

// metro.config.js
const {getDefaultConfig} = require('metro-config');

module.exports = (() => {
  const config = getDefaultConfig(__dirname);
  config.resolver.unstable_enablePackageExports = true;
  return config;
})();

Using Expo or a monorepo? Mirror the example app:

// example/metro.config.js
const path = require('path');
const {getDefaultConfig} = require('@expo/metro-config');
const {withMetroConfig} = require('react-native-monorepo-config');

const root = path.resolve(__dirname, '..');
const config = withMetroConfig(getDefaultConfig(__dirname), {
  root,
  dirname: __dirname,
});

config.resolver.unstable_enablePackageExports = true;

module.exports = config;

Quick start

import {MarkdownStream} from 'react-native-markdown-stream';

const STREAM_URL = 'https://example.com/chat-stream';

export function ChatMessage() {
  return (
    <MarkdownStream
      source={listenToSSE(STREAM_URL)}
      revealMode="word"
      revealDelay={24}
      enableCodeCopy
      enableImageLightbox
      theme={{
        base: 'dark',
        colors: {
          linkColor: '#4ade80',
          quoteBorderColor: '#22c55e',
        },
      }}
    />
  );
}

function* staticExample() {
  yield '# Hello\n';
  yield 'Streaming markdown arrives chunk by chunk.\n';
}

async function* listenToSSE(url: string) {
  const response = await fetch(url);
  if (!response.body) {
    return;
  }
  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const {value, done} = await reader.read();
    if (done) break;
    yield decoder.decode(value);
  }

  reader.releaseLock();
}

Controlled usage

Pass the content prop to render pre-computed Markdown (and still opt into streaming later if you call onReady controls).

<MarkdownStream content={markdownString} onReady={(controls) => controls.start()} />

Supported stream sources

source accepts any of the following:

  • AsyncIterable<string> or Iterable<string> (generators, async generators).
  • A function returning one of the above (lazy initialisation).
  • A ReadableStream (SSE, fetch with streaming responses).
  • Call controls.appendChunk() manually via the onReady callback for complete control.

All chunks (strings, Uint8Array, objects with toString) are normalised to strings before parsing.

MarkdownStream props

| Prop | Type | Description | | --- | --- | --- | | source | MarkdownStreamSource<string> | Primary stream. Optional if you control content manually. | | content | string | Fully rendered markdown. Bypasses streaming until you call start. | | initialValue | string | Markdown shown before the first chunk arrives. | | theme | 'light' \| 'dark' \| MarkdownTheme \| MarkdownThemeConfig | Pick a preset or merge custom colors; defaults to light theme with transparent background. | | textColor | string | Override the resolved theme's primary text color without redefining the whole palette. | | mutedTextColor | string | Override the resolved theme's muted/secondary text color. | | revealMode | 'chunk' \| 'word' \| 'character' | Controls how new content animates in. | | revealDelay | number | Delay (ms) between reveals; ignored when revealMode="chunk". | | autoStart | boolean | Start streaming as soon as source exists (default true). | | onReady | (controls: UseMarkdownStreamResult) => void | Exposes stream controls (append, reset, start, stop). | | onChunk / onEnd / onError | callbacks | Tap into stream lifecycle events. | | showCodeLineNumbers | boolean | Adds line numbers to fenced code blocks. | | enableCodeCopy | boolean | Shows a copy action on code blocks (uses clipboard when available). | | codeCopyLabel | string | Custom label for the copy button. | | onCodeCopy | ({value, language}) => boolean \| void | Return true to mark the copy action as handled. Fires before the built-in clipboard logic. | | onImagePress | ({url, alt}) => boolean \| void | Intercept image taps. Return true to skip built-in lightbox. | | enableImageLightbox | boolean | Presents a modal preview when images are tapped. | | onBlockLongPress | ({node}) => void | Receive long-press events for any block node. | | blockLongPressDelay | number | Milliseconds before the long-press fires (default 300). | | components | Partial<MarkdownRendererComponents> | Override individual renderers (codeBlock, inlineCode, mathBlock, image, …). |

Customising the renderer

Every major element exposes a customization hook:

  • Provide components to override code/math/image rendering with your own component tree.
  • Style code blocks via CodeBlock containerStyle/codeStyle props if you bring your own.
  • Hook into onImagePress, enableImageLightbox, and onBlockLongPress for richer media UX.
  • Implement your own copy logic with onCodeCopy (e.g. analytics or custom tooltips).

Theming

Light and dark themes ship by default, both with transparent container backgrounds so they blend into your layout. Override a single token or a whole palette by passing a MarkdownTheme or MarkdownThemeConfig, or use the textColor / mutedTextColor props for quick tweaks.

import {MarkdownStream} from 'react-native-markdown-stream';

<MarkdownStream
  theme={{
    base: 'light',
    colors: {
      backgroundColor: 'transparent',
      textColor: '#0f172a',
      linkColor: '#f97316',
      quoteBorderColor: '#fb923c',
    },
  }}
/>;

Call resolveTheme if you need the concrete palette outside the component.

Handling incomplete markdown

Streaming text often arrives with half-typed emphasis or unfinished links. Before parsing we run the content through a small sanitizer that auto-closes common Markdown markers and neutralises broken URLs. The implementation is adapted from Vercel's streamdown parse-incomplete-markdown (Apache-2.0).

Using useMarkdownStream directly

The hook powers the component and can run headless when you need custom rendering.

import {useMarkdownStream} from 'react-native-markdown-stream';

export function CustomRenderer({source}) {
  const stream = useMarkdownStream({
    source,
    revealMode: 'character',
    onChunk: (chunk) => console.log('chunk', chunk.length),
  });

  return (
    <ScrollView>
      <Text>{stream.content}</Text>
      <Button title="Skip animation" onPress={() => stream.setRevealMode('chunk')} />
    </ScrollView>
  );
}

The hook returns the current content, the accumulated fullContent, status flags, and control helpers (appendChunk, reset, start, stop, setRevealMode, setRevealDelay).

Other exports

import {
  MarkdownRenderer,
  parseMarkdown,
  useMarkdownStream,
  lightTheme,
  darkTheme,
  resolveTheme,
  type MarkdownTheme,
  type MarkdownThemeConfig,
  type ThemeMode,
  type MarkdownRendererComponents,
} from 'react-native-markdown-stream';

Use MarkdownRenderer when you already have an mdast Root, or to wrap custom parsed content.

Running the example app

yarn install
yarn example          # starts Expo in the example/ workspace

# or target a platform directly
yarn workspace react-native-markdown-stream-example android
yarn workspace react-native-markdown-stream-example ios

The example showcases reveal modes, code copy actions, image lightbox, and long-press handlers.

Architecture support

The package is JavaScript-only and works unchanged in both the classic bridge and the New Architecture (Fabric/TurboModules). No native modules, pods, or Gradle steps are required.

Contributing


© Enes Ünlüer