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 ± b 2 − 4 a c 2 a x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a} x = 2 a − b ± b 2 − 4 a c 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:
Parse Markdown
Marked.js detects $...$ and $$...$$ patterns using custom extensions
Generate HTML
Extensions create .math-inline or .math-display elements with source stored in data attributes
Render with KaTeX
renderMathEquations() finds math elements and calls katex.render() on each
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:
New source is stored in data-math-source attribute
Element text content is updated
renderMathEquations() is called to re-render
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
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.