StreamingMarkdown
Streaming-safe markdown renderer for LLM output. Parses a growing string incrementally, caches settled blocks, and enforces a strict URL policy by default — rendering to a real component tree, never to an HTML string.
Streaming answer
The renderer parses markdown as it arrives — settled blocks are cached and never re-render, so a long answer stays cheap to append to.
Zero
{@html}, safe by constructionStrict URL policy on by default
for await (const chunk of stream) render(chunk); 01 Examples
Static markdown — tables and task lists
Release checklist
| Step | Owner | Status |
|---|---|---|
| Freeze | Platform | Done |
| Sign-off | QA | Pending |
Remaining work:
-
Bump the version
-
Regenerate the docs
-
Publish the tag
Citations from sources
Transformers replaced recurrence with self-attention
, and later work mapped how quality scales with model and dataset size . A bare marker like [3] with no matching source stays plain text.Hostile input, neutralized
A normal link renders as a real link.
A scheme-smuggled link becomes inert text with a dotted underline — the URL never reaches the DOM.
External images are blocked and shown as an alt-text chip: remote tracker
Raw HTML stays literal text, never parsed: <img src=x onerror="alert(1)">
Custom node renderer
Fenced code flows through your own renderer:
export const answer = 42;
02 Customization
Every element maps to a named slot (paragraph, heading1–heading6, inlineCode, codeBlock, table, …). Restyle any of
them via slotClasses, or register a reusable look as a preset on BlocksProvider. Reach for renderers only when
you need to replace a whole node type (highlighting, custom links); use slotClasses for pure styling.
The urlPolicy is strict by default. Widen it deliberately and narrowly — allow a
specific image CDN via allowedImagePrefixes, never a broad prefix. Keep the
policy object referentially stable; a new reference re-parses the whole content.
03 Accessibility
Heading hierarchy
Markdown # maps to the DOM level set by headingLevelStart (deeper levels shift along,
clamped at h6). In a chat surface set it to 3 so a message's own headings slot beneath the page <h1> instead of competing with it. Visual sizing
keeps following the author's level independently.
Streaming cursor
The pulsing cursor shown while streaming is true is decorative and carries aria-hidden="true", so screen readers announce only
the text. Its pulse is gated on motion-safe:, so it holds still under prefers-reduced-motion.
Scrollable tables
Wide tables scroll inside a focusable region (tabindex="0") labelled by tableRegionLabel, so keyboard users
can reach and scroll the overflow (WCAG 2.1.1). Horizontal scroll stays inside the block —
never the page.
Blocked links and images
A policy-blocked link renders as inert text with a dotted underline as the "this was a link" cue; a blocked image becomes an alt-text chip. The blocked state is visible, not silent — the reader can tell something was withheld.
04 API Reference
No matching properties |