TeX does not run in a browser. So the tools that put LaTeX mathematics on a web page — MathJax and KaTeX — are both re-implementations of TeX’s math typesetting in JavaScript, and the two made opposite bets on the same problem. Setting the same 500 formulas here took KaTeX 45 milliseconds and MathJax 264. Choose on speed alone, though, and something else will trip you: neither of them, by default, treats $...$ as mathematics at all. This page covers the difference in design, the error strings you will actually see, and whether the TeX you paste into a page is still there afterwards.
KaTeX vs MathJax: which one to put on the page
Many formulas and speed matters: KaTeX. You want the LaTeX you wrote to pass through as written: MathJax. The speed difference is not a matter of who optimised harder; it is the shape of the API. KaTeX’s katex.renderToString(...) returns a string synchronously — you have the HTML at the moment you call it, so nothing gets injected later and nothing reflows. MathJax, in the browser, is built around MathJax.typesetPromise(), which does what its name says and returns a promise. That brief glimpse of raw \frac{1}{2} a reader sometimes gets is a side effect of the asynchrony.
<!-- MathJax 3: configure BEFORE the script tag loads -->
<script>
window.MathJax = {
tex: { inlineMath: [["$", "$"], ["\\(", "\\)"]] }
};
</script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<!-- KaTeX: stylesheet, engine, then the auto-render pass -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex/dist/contrib/auto-render.min.js"
onload="renderMathInElement(document.body);"></script>Why $...$ does not render, and the comment in KaTeX’s own source
Neither library treats $...$ as inline math out of the box. That is not an oversight but a deliberate default. Open the source of KaTeX’s auto-render and the $ entry is sitting right there, commented out, with the reason on the line above it: “LaTeX uses $…$, but it ruins the display of normal $ in text”. Two currency signs in a paragraph and everything between them becomes a formula. MathJax’s defaults agree: inlineMath is only \(...\), while displayMath is $$...$$ and \[...\]. A good share of all “my math shows up as raw source” reports come down to exactly this.
// KaTeX auto-render, default delimiters (dist/contrib/auto-render.js):
// { left: "$$", right: "$$", display: true }
// { left: "\\(", right: "\\)", display: false }
// { left: "\\[", right: "\\]", display: true }
// plus \begin{equation} \begin{align} \begin{alignat} \begin{gather} \begin{CD}
//
// and this line is deliberately commented out in the source:
// // LaTeX uses $...$, but it ruins the display of normal `$` in text:
// // {left: "$", right: "$", display: false},
// turn it on yourself only if the page has no currency amounts:
renderMathInElement(document.body, {
delimiters: [
{ left: "$$", right: "$$", display: true },
{ left: "$", right: "$", display: false },
{ left: "\\(", right: "\\)", display: false },
{ left: "\\[", right: "\\]", display: true }
],
ignoredTags: ["script", "noscript", "style", "textarea", "pre", "code"]
});What KaTeX cannot do, and the error strings it prints
KaTeX supports a subset of LaTeX math, and anything outside it is thrown as an exception. The wording is consistent: KaTeX parse error: Undefined control sequence: \eqref at position 1: and so on. The most painful gap is cross-referencing equation numbers — neither \label nor \eqref is defined, so a document that numbers its equations and refers back to them cannot stand on bare KaTeX. Chemistry does not pass either: \ce{H2O} needs the separate mhchem extension. The other one people hit constantly is KaTeX parse error: {align} can be used only in display mode., which appears when an align environment is written inside inline delimiters.
It is a misconception, though, that KaTeX cannot do macros. \newcommand, \def and \gdef all work, and if you pass an empty object as the macros option, a definition made with \gdef persists across calls. That is the standard way to inject a site-wide macro preamble exactly once. MathJax, from the other side, bundles about thirty re-implementations of TeX packages — ams, amscd, mathtools, mhchem, cancel, braket, bussproofs, empheq, colortbl among them — and that list is what “broader compatibility” actually means. And if you would rather not let one bad formula break the page, remember throwOnError: false: instead of throwing, KaTeX prints the offending source in red (#cc0000) and carries on.
| What you wrote | KaTeX | MathJax |
|---|---|---|
\frac \int \underbrace \text | fine | fine |
\newcommand \def \gdef | works (persists via macros) | works (via the macros config) |
\label \eqref | no — Undefined control sequence | works (numbering via the tags option) |
\ce{H2O} | needs the separate mhchem extension | bundled |
align (inline) | {align} can be used only in display mode. | fine |
Is MathML usable now? The gap closed in 2023
Every major browser can display MathML. The seat empty the longest was Chrome’s: support went in, came out again, and returned for good in version 109 (Edge from the same 109). Firefox has had it since version 2 and Safari since 10. That said, there is still little occasion to write MathML by hand. What matters more is that both libraries put MathML in their output. KaTeX always sets a <math> element beside the visible HTML, and MathJax’s standard bundle tex-mml-chtml.js loads the extension that adds MathML for assistive technology from the start. That hidden layer is why a screen reader can read the formula at all.
Does the TeX survive? Which one you can copy math back out of
With KaTeX it survives; with MathJax, by default, it does not. Every MathML tree KaTeX emits contains an <annotation encoding="application/x-tex">, and inside it sits the original TeX. Feed it x^2+1 and the string x^2+1 is still somewhere in the output. On top of that the distribution ships a copy-tex extension whose own source comment reads “Replace .katex elements with their TeX source” — load it and selecting a formula puts $x^2+1$ on the clipboard instead of a row of glyphs. MathJax’s ordinary HTML output does not carry the original TeX. Its answer is the right-click menu instead: “Show Math As”, from which the original form can be recovered.
<!-- what KaTeX puts in the DOM for x^2+1 -->
<span class="katex"><span class="katex-mathml"><math
xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow>
<msup><mi>x</mi><mn>2</mn></msup><mo>+</mo><mn>1</mn>
</mrow><annotation encoding="application/x-tex">x^2+1</annotation>
</semantics></math></span><span class="katex-html" aria-hidden="true">...</span></span>
<!-- load this and Ctrl-C on a formula yields $x^2+1$, not glyphs -->
<script defer src="https://cdn.jsdelivr.net/npm/katex/dist/contrib/copy-tex.min.js"></script>Getting your own macros as far as the browser
This is the boundary with the tools that convert a whole document to HTML. MathJax and KaTeX both receive only fragments of mathematics; neither reads your preamble. So \newcommand{\R}{\mathbb{R}} at the top of your .tex never reaches the browser, and \R shows up undefined and red. Macros have to be handed over separately, through the configuration object. The same trap waits on the converter side: make4ht’s mathjax mode leaves the math as LaTeX in the HTML, so your own macros are not expanded there either. The converter side is the business of “LaTeX → HTML”, but the remedy is the same in both places — keep the macro definitions in one file and feed it to TeX and to JavaScript alike.
// KaTeX: one shared object, and \gdef survives from call to call
const macros = {};
katex.renderToString("\\gdef\\R{\\mathbb{R}}", { macros });
katex.renderToString("f\\colon \\R \\to \\R", { macros }); // \R resolves
// or declare them up front, the same way for the auto-render pass:
renderMathInElement(document.body, {
macros: { "\\R": "\\mathbb{R}", "\\eps": "\\varepsilon" },
throwOnError: false // print the bad source in red, do not break the page
});
// MathJax 3: the equivalent lives in the config object
window.MathJax = {
tex: {
macros: { R: "\\mathbb{R}", eps: "\\varepsilon" },
tags: "ams" // this is what enables \label and \eqref
}
};- Math shows up as raw source → suspect the delimiters.
$...$is off by default in both KaTeX and MathJax. - The document uses
\eqref→ choose MathJax and settags: "ams". KaTeX has no numbering machinery at all. - A listing page with hundreds of formulas → KaTeX. Synchronous rendering means the layout does not jump afterwards.
- You have custom macros → pass them in
macros. Your preamble never reaches the browser. - One bad formula must not take the page down →
throwOnError: falsein KaTeX: it prints in red and moves on. - You want the whole document on the web → that is not a math renderer’s job (see “LaTeX → HTML”).