Any chatbot that answers math questions must render LaTeX in the browser. Two libraries dominate this job: KaTeX and MathJax. Both turn TeX source like \frac{a}{b} into formatted output, but they make very different trade-offs. KaTeX is fast, synchronous, and small; MathJax is slower, larger, and far more complete in its TeX coverage and accessibility support. Choosing between them is a real engineering decision, and for a browser-based, privacy-first app the decision affects bundle size, first-render latency, and how streaming output feels.
ReLU.chat renders math with KaTeX and streams responses in roughly 40-character chunks via requestAnimationFrame — an architecture where render speed is not a nice-to-have but a constraint on the whole streaming loop.
What Each Library Is
KaTeX is a focused JavaScript library from Khan Academy. It renders a deliberately scoped subset of TeX to HTML and CSS, synchronously, and it is fast: a full page of math typesets in a few milliseconds. Its output is static HTML — no layout pass needed after insertion — which makes it ideal for content you control.
MathJax is a full TeX/LaTeX engine. Version 3 typesets asynchronously, produces MathML as well as HTML output, and ships a speech-rule engine that reads math aloud for screen readers. It handles essentially the whole TeX language, including obscure environments and packages that KaTeX deliberately does not implement.
The size difference is the first thing that shows in a bundle audit. KaTeX's JavaScript plus its fonts total a few hundred kilobytes. MathJax's complete component set runs into the megabyte range even before fonts. On a mobile connection that difference is a loading-time difference; on a page that streams chat responses, it is also a startup-time difference.
Speed and the Rendering Model
KaTeX renders synchronously: you call katex.render (or katex.renderToString) and the HTML comes back immediately. That makes it trivial to integrate with a streaming renderer — when a chunk completes a math expression, you typeset just that expression and insert it.
MathJax v3 typesets asynchronously: you call MathJax.typesetPromise() and it walks the DOM, finds math, and replaces it in batches. It is smart about incremental updates — v3 keeps track of which elements it already processed — but the async model still means math appears a beat after the text around it, and re-typesetting a growing stream needs care to avoid layout thrash.
A minimal KaTeX render:
import katex from "katex";
function renderMath(el, tex) {
el.innerHTML = katex.renderToString(tex, {
throwOnError: false, // show source instead of throwing
displayMode: true, // block-level math
output: "html"
});
}
A minimal MathJax render:
MathJax = {
tex: { inlineMath: [["$", "$"]], displayMath: [["$$", "$$"]] }
};
// after dynamic content is added:
await MathJax.typesetPromise();
The practical consequence for chat: with KaTeX, a message that ends with an unclosed \frac{ fails gracefully via throwOnError: false, showing source text; with MathJax you can re-run typesetting on any DOM subtree, but you pay for the full parse each time if you are not careful.
TeX Coverage and Accessibility
KaTeX implements most of the TeX that appears in practice — fractions, integrals, matrices, align environments, \begin{cases}, Greek letters, \text — and its coverage keeps growing. But it is a subset by design. Some package macros, rarely used environments, and certain spacing rules behave differently or are missing. If your content is written by your own team, that subset is usually enough, and you learn the boundaries quickly.
MathJax aims for full TeX compatibility, including \label/\ref, custom macros via \def, and a long tail of packages. If your chatbot must render arbitrary user-supplied LaTeX — students pasting homework — MathJax is the safer choice because unknown input is more likely to render than to break.
One practical middle path: validate math source at write time. If your chatbot composes its answers from curated knowledge fragments (as ReLU.chat does), the LaTeX in those fragments is known-good, and a coverage subset like KaTeX's is a feature, not a limitation — it catches malformed math at authoring time instead of at render time.
Accessibility is the strongest argument for MathJax. Its speech-rule engine (built on Speech Rule Engine / SRE) produces spoken math that respects nesting and operator precedence, and its MathML output gives assistive technology a structured representation of the expression. KaTeX has improved here — recent versions support MathML output via output: "mathml" and let you set an aria-label on rendered math — but it does not ship a speech-rule engine, so screen-reader behavior depends on the browser and the assistive stack.
If your site targets WCAG math accessibility, plan for it explicitly regardless of library: provide a textual fallback or aria-label for complex expressions, and test with a real screen reader rather than assuming the library covers it.
Streaming Output and How to Choose
Streaming chat and math rendering interact in a subtle way. A response arrives as a sequence of chunks — ReLU.chat reveals roughly 40 characters per animation frame — and a math expression may span many chunks. If you run KaTeX on the whole message body after every chunk, you waste work and cause visible flicker. If you run it only on the final message, math appears only after the response completes, which feels slow.
The workable pattern is incremental: render plain text as it streams, and typeset only when a math span is complete (its closing $ or \] has arrived). For KaTeX this is cheap because each render is synchronous and scoped to one element. For MathJax, debounce typesetPromise calls and avoid re-scanning already-rendered subtrees:
let mathTimer;
function onStreamChunk(container) {
clearTimeout(mathTimer);
// defer typesetting until the stream pauses or completes
mathTimer = setTimeout(() => MathJax.typesetPromise([container]), 120);
}
So how do you choose? Use KaTeX when you control the content (curated fragments, known-good LaTeX), bundle size and first-render speed matter, and you are rendering inside a streaming loop where synchronous, scoped renders are a big win. Use MathJax when users supply arbitrary LaTeX, you need MathML and speech-rule accessibility out of the box, or you rely on TeX features outside KaTeX's subset.
For ReLU.chat, KaTeX fits the architecture: fast synchronous rendering inside the requestAnimationFrame streaming loop, a small footprint consistent with on-device, privacy-first design, and content that comes from curated knowledge fragments where the LaTeX is already validated.
Key Takeaway
KaTeX wins on speed, size, and streaming integration; MathJax wins on TeX completeness and built-in accessibility. Decide based on who writes the math and how the output renders — for a streaming, content-curated, browser-based chatbot, KaTeX is the pragmatic default, with MathJax as the right upgrade when user-supplied LaTeX or speech-rule accessibility becomes a requirement.