Skip to content

Chat Markdown

Render streaming Markdown with highlighted code, tables and a writing caret.

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Chat Markdown renders a model's answer while it's still arriving. Every assistant Chat Message uses it for its content, and reasoning uses it too. Use it on its own for any other place a model writes: a summary panel, an inline explanation, a generated description.

import { ChatMarkdown } from '@youj/react'

<ChatMarkdown content={answer.stream} streaming={answer.streaming} />

content is a string or a ChatTextSource. With a source, a token re-renders only this component, and inside it only the block the token lands in: blocks are memoised by their source text.

Reference

Propscontent, streaming, incomplete… (5)

Props

ChatMarkdown props
PropType
contentRequiredChatText

Markdown source: a string, or a ChatTextSource that streams it.

–
streamingboolean

Still being written: words fade in, a caret marks the end, unfinished syntax renders optimistically.

default false

false
incompleteboolean

Cut off (stopped, or failed part-way): the unfinished end still renders as it did while streaming, without the caret.

default false

false
sourcesChatSource[]

Numbered citations: [1] in the text becomes a chip for sources[0], previewed on hover.

–
headingLevelnumber

The outline level of the message's top heading, whatever markdown level the model used for it (models often start at ##); deeper ones follow.

default 2

2
Other props pass through: extends ComponentProps<'div'>.
MotionWord arrives, caret blink, link hover · reduced motion

Motion

Motion tokens
MomentTokens
Word arrivesfades up once as it mounts (@starting-style), and costs nothing after
duration.slowease.enter

340ms · cubic-bezier(0.16, 1, 0.3, 1)

340mscubic-bezier(0.16, 1, 0.3, 1)
Caret blinkonly after the stream pauses; one on-off cycle per 2 × deliberate
duration.deliberateease.linear

560ms · cubic-bezier(0, 0, 1, 1)

560mscubic-bezier(0, 0, 1, 1)
Link hoverthe underline takes the accent
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)

Reduced motion

No fades and no blink. The text is revealed in whole sentences and lines rather than word by word.

Reduced motion

Words don't fade and the caret doesn't blink. A streaming answer is shown up to its last finished sentence or line, so it arrives in calm chunks instead of a constant trickle. revealedUnderReducedMotion(text) is that rule, exported.

AccessibilityKeyboard, announcements

Accessibility

  • Output is real structure: headings at the level you choose, lists, tables with header cells, and code in <pre><code>.
  • Code blocks and wide tables scroll inside a named scroll area ("TypeScript code", "Table"), which is a tab stop once it overflows.
  • The caret and the fades are decoration. Inside a Chat Message List, the finished answer is announced once as plain text, never token by token.