A fully featured Markdown renderer for React. From README files to streaming LLM output, one component renders it all:
- LaTeX rendering for math
- 30+ languages highlighted
- Under 12kb, 0 dependencies
- GitHub Flavored Markdown
- Streaming-safe rendering
- 500+ tests, 99.9% coverage
npm install llmrender
# Release 1.0 is available
Now **faster** and ~~heavier~~ lighter. Even $E = mc^2$ renders inline.
$$
\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}
$$
You also have **30+ languages** supported in the default Syntax Highlighting:
```jsx
import Markdown from "llmrender";
const App = ({ text }) => <Markdown>{text}</Markdown>;
```
your markdown what your users see
Try it live
Edit the Markdown on the left, or press ▶ stream to watch it render token by token, exactly the way your LLM output arrives.
Syntax highlighting
30+ languages, no highlighter to install
Fenced code blocks come out colored without adding highlight.js or Prism to your bundle. JavaScript, TypeScript, Python, Rust, Go, SQL, CSS, HTML, shell, diff and many more are recognized out of the box.
- Four ready-made themes: light, dark, adaptive and high-contrast
- Unknown languages degrade gracefully to plain code blocks
-
Prefer your own stack? Pass any function to the
highlightprop and plug in Prism.js in three lines
// Built in, just write Markdown
<Markdown>{text}</Markdown>
// …or bring your own highlighter
<Markdown
highlight={(code, lang) => (
<SyntaxHighlighter language={lang}>
{code}
</SyntaxHighlighter>
)}
>
LaTeX math
LaTeX in, native MathML out
LLMs love emitting LaTeX. LLMRender parses it and renders browser-native MathML: fractions, roots, matrices, Greek letters, sums, integrals and accents.
- Inline
$…$and display$$…$$syntax - Crisp at any zoom level and accessible to screen readers
-
Want KaTeX anyway? Pass it through the
mathprop and keep everything else
Inline $e^{i\pi} + 1 = 0$ math.
$$
\sum_{n=1}^{\infty} \frac{1}{n^2} = \frac{\pi^2}{6}
$$
Inline math.
GitHub Flavored Markdown
The Markdown everyone actually writes
From READMEs to chat replies, real-world Markdown is GitHub flavored, and
LLMRender renders all of it: tables with column alignment, task lists with
checkboxes, strikethrough, autolinks, and the
[!NOTE]/[!TIP]/[!WARNING]
callouts that make documents scannable.
- Headings get ids and anchor links for deep-linking
- Nested lists, blockquotes and code inside blockquotes
- Plus extras like ruby annotations for East-Asian text
> [!WARNING]
> Callouts render with icons and colors.
| Model | Score |
|:------|------:|
| A | 98.2 |
- [x] Tables, aligned
- [x] Task lists
- [x] ~~Missing features~~
Streaming
Made for tokens arriving mid-sentence
Streaming output means the renderer sees half-finished Markdown hundreds of times per response. LLMRender is designed for that: just re-render with the text you have so far, with no buffering, no flicker, no crashes.
- Unclosed code fences render as code while they stream
- Unclosed
$$math blocks render as math - Fast enough to re-parse the whole message on every chunk
const [text, setText] = useState("");
for await (const chunk of stream) {
setText((t) => t + chunk);
}
// Re-render on every chunk: unclosed
// ``` fences and $$ blocks mid-stream
// render gracefully, never break
return <Markdown>{text}</Markdown>;
Security
Safe with untrusted content by default
Whether it comes from users, files or models, Markdown can carry malicious
payloads, so LLMRender is
safe by default:
javascript: and data: URLs are neutralized, raw
HTML is ignored unless you opt in, and even then it goes through a strict
tag and attribute allowlist.
- No
dangerouslySetInnerHTMLanywhere, ever -
rawHtmlblocks scripts, event handlers, iframes and styles even when enabled -
target="_blank"links getrel="noopener noreferrer"against tabnabbing
// Safe by default: HTML ignored,
// dangerous URLs neutralized
<Markdown>{untrusted}</Markdown>
// Opt in to a safe HTML allowlist
<Markdown rawHtml>{untrusted}</Markdown>
// …or define exactly what's allowed
<Markdown rawHtml={{ b: [], a: ["href"] }}>
{untrusted}
</Markdown>
Styling
It's just a <div>, style it your way
The output is a plain <div> with semantic HTML
inside, so Tailwind Typography, Styled Components, CSS Modules or plain
stylesheets all work with zero adapters.
-
Four importable themes:
default,dark,adaptive(followsprefers-color-scheme) andcontrast(WCAG AAA) - Theme colors exposed as
--llmrender-*CSS variables -
Every
<div>attribute passes through:className,style,data-*…
// Pick a theme…
import "llmrender/themes/adaptive.css";
// …then style it like any other div
<Markdown
className="prose dark:prose-invert"
id="answer"
data-testid="chat-message"
>
{text}
</Markdown>
Bundle size
A fraction of the usual stack
Markdown + math + highlighting + sanitizing, measured as real bundle overhead in identical Vite builds (methodology):
| Package | Base | Math | Highlight | Sanitize | Full size (gzip) |
|---|---|---|---|---|---|
| LLMRender | 9 KB | built-in | built-in | built-in | 12 KB |
react-markdown |
33.3 KB | +74.6 KB | +508 KB | built-in | 340 KB |
marked |
12.1 KB | +74.6 KB | +298 KB | +8.7 KB | 403 KB |
markdown-it |
43.3 KB | +74.6 KB | +298 KB | +8.7 KB | 436 KB |
Install it now
npm install llmrender