# LLMRender [](https://www.npmjs.com/package/llmrender) [](https://github.com/franciscop/llmrender/actions) [](https://github.com/franciscop/llmrender/blob/master/index.min.js) [](https://github.com/franciscop/llmrender/blob/master/package.json)
A React Markdown renderer packed with features for all your LLM output:
```jsx
import "llmrender/themes/default.css";
import Markdown from "llmrender";
export default function BlogPost({ text }) {
return
);
}
config example` with a `` header. The included themes style them out of the box; you can also target them directly: ```css .callout-note { border-left: 4px solid #0969da; background: #ddf4ff; } .callout-warning { border-left: 4px solid #d1242f; background: #ffebe9; } ``` ### Lists ```md - Apples - Oranges - Navel - Blood - Bananas 1. Preheat oven to 200°C 2. Mix the dry ingredients 1. Flour 2. Baking powder 3. Fold in the wet ingredients - [x] Design the API - [x] Write tests - [ ] Publish to npm ``` Task items render with a disabled ``. ### Code blocks ````md ```ts async function fetchUser(id: string): Promise
{ const res = await fetch(`/api/users/${id}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); } ``` ```` Supported languages include: `js`/`ts`/`jsx`/`tsx`, `py`, `go`, `rust`, `java`, `c`/`cpp`/`cs`, `html`, `css`/`scss`, `json`, `bash`/`sh`, `sql`, `yaml`, `svelte`, `vue`, and more. ### Tables ```md | Language | Paradigm | Typing | First appeared | |:-----------|:------------:|--------:|---------------:| | TypeScript | Multi | Static | 2012 | | Python | Multi | Dynamic | 1991 | | Haskell | Functional | Static | 1990 | ``` Column alignment: `:---` left, `:---:` center, `---:` right. Inline markup and escaped pipes (`\|`) work inside cells. ### Math Inline with `$…$`, display with `$$…$$`: ```md Einstein's mass-energy equivalence $E = mc^2$ is one of the most famous equations in physics. The quadratic formula gives the roots of $ax^2 + bx + c = 0$: $$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$ A Gaussian integral: $$\int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}$$ And the sum of the first $n$ natural numbers: $$\sum_{k=1}^{n} k = \frac{n(n+1)}{2}$$ ``` ### Horizontal rule ```md --- ``` ## Examples ### Tailwind Typography ```jsx {content} ``` ### Styled Components ```jsx import styled from "styled-components"; import Markdown from "llmrender"; const Article = styled(Markdown)` font-family: Georgia, serif; line-height: 1.7; h1, h2, h3 { font-weight: 700; margin-top: 1.5em; } a { color: #0969da; text-decoration: underline; } pre { background: #f6f8fa; padding: 1em; border-radius: 6px; overflow-x: auto; } blockquote { border-left: 4px solid #d0d7de; padding-left: 1em; color: #57606a; } `; export default function Post({ content }) { return{content} ; } ``` ### Prism.js > [!TIP] > We're under active development, so if you find a bug with the default syntax highlighter we suggest [you file a ticket](https://github.com/franciscop/llmrender/issues). To replace the built-in highlighter with Prism: ```bash npm i prismjs ``` ```jsx import Markdown from "llmrender"; import Prism from "prismjs"; import "prismjs/themes/prism.css"; import "prismjs/components/prism-typescript"; import "prismjs/components/prism-python"; function highlight(code, lang) { const grammar = lang && Prism.languages[lang]; const highlighted = grammar ? Prism.highlight(code, grammar, lang) : code; return (); }{content} ``` ### KaTeX > [!TIP] > We're under active development, so if you find a bug with the default math renderer we suggest [you file a ticket](https://github.com/franciscop/llmrender/issues). For full LaTeX beyond the built-in renderer, you can add the full Katex: ```bash npm i katex ``` ```jsx import Markdown from "llmrender"; import katex from "katex"; import "katex/dist/katex.min.css"; function renderMath(tex, block) { const html = katex.renderToString(tex, { displayMode: block, throwOnError: false }); return ; }{content} ``` ### Mermaid diagrams Intercept ` ```mermaid ` blocks via the `highlight` prop: ```jsx import Markdown, { highlightCode } from "llmrender"; import mermaid from "mermaid"; import { useEffect, useRef } from "react"; mermaid.initialize({ startOnLoad: false }); function Diagram({ code }) { const ref = useRef(null); useEffect(() => { if (ref.current) mermaid.run({ nodes: [ref.current] }); }, [code]); return{code}; } function highlight(code, lang) { if (lang === "mermaid") return []; return highlightCode(code, lang); } {content} ``` ````md ```mermaid graph TD A[Start] --> B{Decision} B -->|Yes| C[Do it] B -->|No| D[Skip] ``` ```` ### Streaming LLM output Render tokens as they arrive. Pass the partial string and LLMRender handles incomplete Markdown gracefully: ```jsx import { useChat } from "@ai-sdk/react"; import Markdown from "llmrender"; export default function Chat() { const { messages, input, handleInputChange, handleSubmit } = useChat(); return ({messages.map((m) => (); } ``` ### Chat messages Render a conversation where each assistant message is Markdown. User messages are plain text: ```jsx import Markdown from "llmrender"; const messages = [ { role: "user", content: "What is the quadratic formula?" }, { role: "assistant", content: "The quadratic formula solves $ax^2 + bx + c = 0$:\n\n$$x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}$$\n\nwhere $a \\neq 0$.", }, ]; export default function ChatThread() { return ({m.role === "user" ? ())}{m.content}
) : ({m.content} )}{messages.map((m, i) => (); } ``` For streaming output as the AI types, see [Streaming LLM output](#streaming-llm-output). ### Next.js LLMRender works as a **React Server Component** for static content, no `"use client"` needed: ```tsx // app/blog/[slug]/page.tsx import Markdown from "llmrender"; import "llmrender/themes/default.css"; export default async function PostPage({ params }: { params: { slug: string } }) { const post = await db.post.findUnique({ where: { slug: params.slug } }); return{m.role === "user" ? ())}{m.content}
) : ({m.content} )}{post.content} ; } ``` Add `"use client"` only when you need interactivity in the same component (streaming, copy buttons, live editor): ```tsx "use client"; import { useChat } from "@ai-sdk/react"; import Markdown from "llmrender"; export default function Chat() { const { messages, input, handleInputChange, handleSubmit } = useChat(); // ... } ``` ### Live editor ```jsx import { useState } from "react"; import Markdown from "llmrender"; export default function Editor() { const [text, setText] = useState("# Hello\n\nStart typing..."); return (); } ``` ### Copy button on code blocks Use the `highlight` prop to wrap each block with a copy button: ```jsx import Markdown, { highlightCode } from "llmrender"; import { useState } from "react"; function CodeBlock({ code, children }) { const [copied, setCopied] = useState(false); const copy = () => { navigator.clipboard.writeText(code); setCopied(true); setTimeout(() => setCopied(false), 2000); }; return ({children}); } function highlight(code, lang) { return [{highlightCode(code, lang)} ]; }{content} ``` ## Security LLMRender is safe to use with untrusted Markdown, including content from users, LLMs, or external APIs. Here is how XSS (cross-site scripting) and other injection attacks are prevented, and what the limits are. LLMRender never produces raw HTML strings. Every piece of content (headings, paragraphs, link text, table cells, code, image alt text) becomes a React element rendered through JSX. React automatically escapes all text children, so input like `[](url)` renders the `