Skip to main content

Math Equations

Markdown-OS supports mathematical equations using KaTeX, a fast math typesetting library. You can write equations in LaTeX syntax with both inline and display modes.

Inline Math

Inline equations are wrapped in single dollar signs: $...$ Example:
The quadratic formula is $x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$ for polynomials.
Renders as: The quadratic formula is x=b±b24ac2ax = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a} for polynomials.
Inline math flows with surrounding text and uses KaTeX’s displayMode: false rendering.

Display Math

Display equations are wrapped in double dollar signs: $$...$$ Example:
$$
\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
$$
Renders as a centered block equation with proper spacing.
Display math is rendered in a dedicated block with edit and copy buttons for easy interaction.

KaTeX Integration

Equations are rendered using KaTeX with custom Marked.js extensions:

Inline Math Extension

const mathInline = {
  name: "mathInline",
  level: "inline",
  start(src) {
    return src.match(/\$/)?.index;
  },
  tokenizer(src) {
    const match = src.match(/^\$([^\$\n]+?)\$(?!\$)/);
    if (match) {
      return {
        type: "mathInline",
        raw: match[0],
        text: match[1].trim(),
      };
    }
  },
  renderer(token) {
    const escaped = escapeHtmlAttribute(token.text);
    return `<span class="math-inline" contenteditable="false" data-math-source="${escaped}">${escaped}</span>`;
  },
};

Display Math Extension

const mathBlock = {
  name: "mathBlock",
  level: "block",
  start(src) {
    return src.match(/\$\$/)?.index;
  },
  tokenizer(src) {
    const match = src.match(/^\$\$[ \t]*\n?([\s\S]+?)\n?\$\$(?:\n|$)/);
    if (match) {
      return {
        type: "mathBlock",
        raw: match[0],
        text: match[1].trim(),
      };
    }
  },
  renderer(token) {
    const escaped = escapeHtmlAttribute(token.text);
    return `<div class="math-display" contenteditable="false" data-math-source="${escaped}">${escaped}</div>\n`;
  },
};
Both extensions store the original LaTeX source in data-math-source attributes, which is used for editing and re-rendering.

Rendering Process

Math equations are rendered during document decoration:
1

Parse Markdown

Marked.js detects $...$ and $$...$$ patterns using custom extensions
2

Generate HTML

Extensions create .math-inline or .math-display elements with source stored in data attributes
3

Render with KaTeX

renderMathEquations() finds math elements and calls katex.render() on each
4

Add Controls

Display equations get edit and copy buttons for interaction

Render Implementation

function renderMathEquations() {
  if (!window.katex || !state.root) {
    return;
  }
  
  // Inline math
  state.root.querySelectorAll(".math-inline").forEach((element) => {
    const source = element.getAttribute("data-math-source") || element.textContent;
    try {
      window.katex.render(source, element, {
        throwOnError: false,
        displayMode: false,
        output: "htmlAndMathml",
      });
    } catch (error) {
      renderMathError(element, source, false);
    }
  });
  
  // Display math
  state.root.querySelectorAll(".math-display").forEach((element) => {
    const source = element.getAttribute("data-math-source") || element.textContent;
    try {
      window.katex.render(source, element, {
        throwOnError: false,
        displayMode: true,
        output: "htmlAndMathml",
      });
      
      // Add edit and copy buttons
      const actions = document.createElement("div");
      actions.className = "math-block-actions";
      // ... button setup ...
      element.appendChild(actions);
    } catch (error) {
      renderMathError(element, source, true);
    }
  });
}

Interactive Features

Copy LaTeX

Display equations have a copy button that copies the raw LaTeX source:
const copyButton = createActionButton("copy", "Copy LaTeX");
copyButton.addEventListener("click", async (event) => {
  event.preventDefault();
  event.stopPropagation();
  try {
    const latestSource = element.getAttribute("data-math-source") || "";
    await copyToClipboard(latestSource);
    flashCopied(copyButton);
  } catch (error) {
    console.error("Failed to copy LaTeX content.", error);
  }
});
The copy button temporarily shows a checkmark after successful copy, then reverts to the copy icon after 1.5 seconds.

Edit Equations

Click the edit button to modify an equation:
const editButton = createActionButton("edit", "Edit equation");
editButton.addEventListener("click", (event) => {
  event.preventDefault();
  event.stopPropagation();
  openBlockEditor("math-display", element);
});
The editor opens a modal with:
  • Title: “Edit display equation” or “Edit inline equation”
  • Textarea with current LaTeX source
  • Save and Cancel buttons
  • Keyboard shortcuts (Enter saves, Escape cancels)
After editing:
  1. New source is stored in data-math-source attribute
  2. Element text content is updated
  3. renderMathEquations() is called to re-render
  4. Change event is emitted for auto-save

Error Handling

Invalid LaTeX syntax is handled gracefully:

Inline Math Errors

function renderMathError(element, source, displayMode) {
  if (displayMode) {
    element.innerHTML = "";
    const errorBlock = document.createElement("div");
    errorBlock.className = "math-error-block";
    errorBlock.textContent = `Invalid LaTeX:\n${source}`;
    element.appendChild(errorBlock);
    return;
  }
  
  element.classList.add("math-error");
  element.title = `Invalid LaTeX: ${source}`;
}
Inline math errors add a visual indicator and tooltip. Display math errors show the error message in a dedicated block.

KaTeX Configuration

KaTeX is configured for safety and compatibility:
window.katex.render(source, element, {
  throwOnError: false,      // Show errors instead of throwing
  displayMode: true,        // Block vs inline rendering
  output: "htmlAndMathml",  // Generate both HTML and MathML
});

Configuration Options

  • throwOnError: false - Renders error messages instead of throwing exceptions
  • displayMode - Controls centered block layout vs inline flow
  • output: "htmlAndMathml" - Generates accessible MathML alongside HTML
MathML output improves accessibility for screen readers and allows better copying of equations.

Supported LaTeX Commands

KaTeX supports most standard LaTeX math commands:

Common Commands

  • Greek letters: \alpha, \beta, \gamma, \Delta, \Omega
  • Operators: \sum, \int, \prod, \lim, \frac
  • Relations: \leq, \geq, \neq, \approx, \equiv
  • Arrows: \rightarrow, \Rightarrow, \leftrightarrow
  • Sets: \in, \notin, \subset, \cup, \cap
  • Logic: \land, \lor, \neg, \forall, \exists

Advanced Features

  • Matrices: \begin{matrix}...\end{matrix}
  • Cases: \begin{cases}...\end{cases}
  • Aligned equations: \begin{aligned}...\end{aligned}
  • Colors: \textcolor{red}{text}, \colorbox{yellow}{text}
  • Sizing: \large, \Large, \huge, \tiny
For a complete list of supported commands, see the KaTeX documentation.

Example Equations

Calculus

$$
\frac{d}{dx}\left(\int_{a}^{x} f(t)\,dt\right) = f(x)
$$

Linear Algebra

$$
\begin{bmatrix}
a & b \\
c & d
\end{bmatrix}
\begin{bmatrix}
x \\
y
\end{bmatrix}
=
\begin{bmatrix}
ax + by \\
cx + dy
\end{bmatrix}
$$

Statistics

$$
\mathbb{E}[X] = \sum_{i=1}^{n} x_i \cdot P(X = x_i)
$$

Physics

$$
E = mc^2 \quad \text{and} \quad F = ma
$$

Markdown Serialization

When saving, math elements are converted back to markdown:
turndownService.addRule("mathDisplay", {
  filter(node) {
    return (
      node.nodeType === Node.ELEMENT_NODE &&
      node.nodeName === "DIV" &&
      node.classList.contains("math-display")
    );
  },
  replacement(_content, node) {
    const source = node.getAttribute("data-math-source") || "";
    return `\n\n$$\n${source}\n$$\n\n`;
  },
});

turndownService.addRule("mathInline", {
  filter(node) {
    return (
      node.nodeType === Node.ELEMENT_NODE &&
      node.nodeName === "SPAN" &&
      node.classList.contains("math-inline")
    );
  },
  replacement(_content, node) {
    const source = node.getAttribute("data-math-source") || "";
    return `$${source}$`;
  },
});
The original LaTeX source is preserved in data attributes, ensuring perfect round-trip serialization.

Best Practices

Use display mode for complex equations: Long equations are easier to read when centered in display mode.
Test equations before saving: The live preview shows how your equation will render, but you can also edit and preview multiple times before saving.
Escape special characters: Use \{, \}, \_ to render literal braces and underscores in LaTeX.
Performance: KaTeX is much faster than MathJax, rendering equations instantly even with many equations on a page.

Build docs developers (and LLMs) love