Markdown that draws while the model is still writing

flowdown renders an AI reply in React as it streams: GitHub Markdown plus steps, tabs, figures, diagrams and math. Its own parser, no dependencies, 31.7 KB.

npm install flowdown

React 18 and 19 · MIT · Docs · also outside React

Streaming a reply

  1. Install
    One package, no dependencies.
    sh
    npm install flowdown
    
  2. Render
    Pass streaming while the text is still arriving.
    reply.tsxtsx
    import { Markdown } from 'flowdown'
    import 'flowdown/styles.css'
    
    export function Reply({ text, done }) {
      return <Markdown streaming={!done}>{text}</Markdown>
    }
    
  3. Teach the model
    Add answerFormatPrompt() to the system prompt, and it writes these forms.
  • 0.13 sto stream 100k characters
  • 31.7 KBgzipped
  • 0dependencies

The same reply, outside React:

ts
import { renderToHtml } from 'flowdown/static'

const html = renderToHtml(reply)

Streaming 100k charactersTime
flowdown0.13 s
markdown-to-jsx2.3 s
Streamdown6.3 s

Read more

1,666 characters

Built for a reply, not a document

Six things it does

Streaming

Nothing half-written reaches the reader

A reply arrives mid-word. Unfinished **bold, a half-typed link and an open code fence are repaired as they come, so raw syntax never shows and nothing flickers when it closes.

arrivesis drawn asThe **answer isThe answer is  (bold)see [the guisee the gui```ts⏎const a =a code block, still open| a | b |⏎| -a table, its first row$$ \frac{1held until it can be setonly the block being written is parsed again

Dialect

Forms a model can write and a reader can scan

Steps, tabs, headline figures, the case for and against, callouts, folded sections, sources and file trees, on top of GitHub Markdown. Each is a quote line and a list: where it is not rendered, it still reads.

A reply drawn as numbered steps on one line, with code under a step and tabs after them

Speed

A long reply costs what a short one does

A 100k-character reply streams in 0.13 s of main-thread time; the next fastest of six React renderers takes 2.3 s. No update takes more than 5 ms, so a stream never drops a frame.

streaming, mschat 5kcode 50kanswer 100kflowdown1035132markdown-to-jsx14562,293markstream409445,522Streamdown514,9606,265marked-react36787,371react-markdown2042,871stopped31.7 KB gzipped, one package, no dependencies

Targets

The same reply outside React

As HTML text on a server, in a page with no framework, as a <flow-down> element, or as the rows of a terminal. One parser draws them all, and none of these entries imports React.

Diagrams and math

A diagram a reader can open

A mermaid fence is drawn once it is whole, in the page’s colours, and opens in a viewer that zooms and pans. Formulas are set by KaTeX. Both load only for a reply that has one.

Safety

A reply cannot act on the page

A model’s text is untrusted. Raw HTML is parsed and sanitized, scripts and handlers are dropped, and a link keeps only a safe protocol. It needs no setup and no second library.

the reply<img src=x onerror="steal()">[click](javascript:alert(1))<script>fetch('/token')</script>the page<img src="x"/><p><a>click</a></p>(nothing)

Documentation

19 pages →

Releases

Full changelog →
  1. v0.4.3

    A shorter README and package description

    1 change

  2. v0.4.2

    Two replies that stream at once no longer slow each other down. The scan for a fence left open remembered one text for the whole page, so each reply made the other's start over on every token: two 100k-character replies side by side cost twice what they cost one after the other. Each <Markdown> now has a scan of its own.

    1 change

  3. v0.4.1

    Each icon is one <path>. Its strokes are subpaths of a single outline, and its circle or rounded square is written out as arcs, so a callout, a code block's buttons and a diagram's toolbar put fewer elements on the page: a 20k-character reply draws 452 where it drew 498. The icons look the same. A stylesheet that addressed an icon's <circle>, <rect> or one of its strokes by position no longer matches; svg and svg path still do.

    1 change

  4. v0.4.0

    Code blocks have a download button beside the copy button: it saves the code under the fence's title, or as code.<ext> from its language (code.txt when there is none). New label: download. The copy button falls back to the browser's copy command where the Clipboard API is missing or refused (an insecure origin), so it shows everywhere, a server render included. On a touch screen both buttons are 44 px targets. A fence with no language has no empty language label.

    4 changes

  5. v0.3.0

    Folded sections (> [!DETAILS], <details>) are an outlined panel whose content is inset by the panel itself, so text written straight after the summary and a code block inside line up; opening fades the content in. Checklists draw their own box, centred on the line. <kbd> is a keycap. Source numbers are centred on their row.

    1 change

Start in three steps

Free · MIT license

  1. Install

    npm install flowdown. React is its only peer, and only for <Markdown>.

  2. Render

    Import flowdown/styles.css once, then <Markdown streaming={!done}>{text}</Markdown>.

  3. Teach the model

    Add answerFormatPrompt() from flowdown/prompt to the system prompt.