Skip to content

Render maths with KaTeX

Draw the formulas a model writes — $…$, $$…$$, \(…\), \[…\] — while the reply streams, with @aparte/plugin-katex.

On this page
Open
Formulas drawn as the reply streams; the prices stay text.
bash
npm install @aparte/plugin-katex katex
ts
import 'katex/dist/katex.min.css';
import { setupKatex } from '@aparte/plugin-katex';

setupKatex();

A formula is drawn by KaTeX, loaded on the first one. Its stylesheet is yours to import, because its fonts are relative URLs your bundler resolves; without it each formula shows twice, and the plugin says so once in the console.

Maths is recognised by the Markdown provider, before Markdown can read it: to Markdown, \, is an escape and a*b*c is emphasis. @aparte/plugin-marked, streaming or not, and @aparte/plugin-streaming-markdown do it on their own once setupKatex() has run — nothing else to configure. To keep dollars as text whatever is registered, pass math: false to the marked provider.

Both read maths by the rules in the table below; they differ in when a formula appears while the reply streams. @aparte/plugin-marked/streaming draws a $$, \[ or \( formula as it arrives, and a single $ once it closes. @aparte/plugin-streaming-markdown draws a display formula opened by $$ or \[ on its own line as it arrives, and any other once its closer has come. The finished reply is drawn by the one-shot provider — marked, in the recommended setup.

A provider of your own hands maths over by writing the markup remark-math already writes: <code class="language-math math-inline">TeX</code> inline, and <pre><code class="language-math math-display">TeX</code></pre> for a block.

writtendrawn as
$x^2$, \(x^2\)inline
$$x^2$$, \[x^2\], on their own lines or inside a sentencedisplay
$5 and $10text

A single $ follows Pandoc’s rule: a character right after the opening $, one right before the closing $, and no digit right after it. A price stays a price. Code always wins: a $ in backquotes or a fence is code.

A formula is drawn as it arrives. Half a formula — \frac{1}{ — does not parse, so the last drawing that did stays on screen until the next one does, and nothing flashes red mid-stream. A single $ is drawn only once it closes, since until then it could be a price. Once the reply is finished, a formula that still does not parse shows its TeX, quietly, with KaTeX’s message as a tooltip.

trust is always off, so a formula cannot carry a link (\href) or set a class or a style (\htmlClass); maxSize and maxExpand are capped, so a size cannot pass 10em and a macro cannot expand forever; KaTeX does not cap a negative shift, so a formula taller than 100em — \raisebox{-10000em}{x}; a 30-row matrix is under 40 — shows as its TeX; every formula is painted inside its own box, so \smash, \raisebox or a negative \kern cannot lay one over the chat around it; and every formula gets a fresh set of macros, so a \gdef in one reply cannot reach the next. KaTeX’s own options pass through setupKatex({ katex: { … } }); these are kept whatever they say.

@aparte/plugin-katex/core imports nothing from katex: hand it the instance, or a function that loads it.

ts
import katex from 'katex';
import { setupKatexWith } from '@aparte/plugin-katex/core';

setupKatexWith(katex, { live: false });

KatexOptions takes live (false draws a formula once it has closed), katex and maxLength. Any object shaped like KatexLike will do. Both setups return a function that removes the renderer.