Skip to main content

Overview

The Math extension enables rendering of mathematical equations using LaTeX syntax. It supports both inline math expressions and block-level math equations with custom rendering functions (typically using KaTeX or MathJax).

Installation

import { defineMath } from '@prosekit/extensions'
You’ll also need a math rendering library like KaTeX:
npm install katex

Basic Usage

import { defineMath } from '@prosekit/extensions'
import { createEditor } from '@prosekit/core'
import 'katex/dist/katex.min.css'

const editor = createEditor({
  extensions: [
    defineMath({
      renderMathBlock: (content) => {
        const element = document.createElement('div')
        katex.render(content, element, {
          displayMode: true,
          throwOnError: false,
        })
        return element
      },
      renderMathInline: (content) => {
        const element = document.createElement('span')
        katex.render(content, element, {
          displayMode: false,
          throwOnError: false,
        })
        return element
      },
    }),
    // ... other extensions
  ],
})

Math Options

The defineMath function requires two render functions:
interface MathOptions {
  /**
   * The function to render block-level math equations.
   */
  renderMathBlock: RenderMathBlock
  
  /**
   * The function to render inline math expressions.
   */
  renderMathInline: RenderMathInline
}

type RenderMathBlock = (content: string) => HTMLElement
type RenderMathInline = (content: string) => HTMLElement

Inline Math

Inline math expressions are rendered within text flow.

Creating Inline Math

Type $ followed by LaTeX syntax and another $:
The equation $E = mc^2$ is famous.

Programmatic Usage

import { defineMathInline } from '@prosekit/extensions'

const extension = defineMathInline({
  render: (content) => {
    const span = document.createElement('span')
    katex.render(content, span, { displayMode: false })
    return span
  },
})

Input Rule

Inline math automatically activates when you type $...$:
Type: $x^2 + y^2 = z^2$

Block Math

Block math equations are displayed on their own line with centered formatting.

Creating Block Math

Type $$ on a new line, press Enter, then type your LaTeX:
$$
\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}
$$

Programmatic Usage

import { defineMathBlock } from '@prosekit/extensions'

const extension = defineMathBlock({
  render: (content) => {
    const div = document.createElement('div')
    katex.render(content, div, {
      displayMode: true,
      throwOnError: false,
    })
    return div
  },
})

Block Attributes

Math blocks support a language attribute (default: 'tex'):
{
  language: 'tex', // For syntax highlighting
}

KaTeX Integration

Complete example using KaTeX:
import { createEditor } from '@prosekit/core'
import { defineMath } from '@prosekit/extensions'
import katex from 'katex'
import 'katex/dist/katex.min.css'

const editor = createEditor({
  extensions: [
    defineMath({
      renderMathBlock: (content) => {
        const container = document.createElement('div')
        try {
          katex.render(content, container, {
            displayMode: true,
            throwOnError: false,
            errorColor: '#cc0000',
            macros: {
              '\\RR': '\\mathbb{R}',
            },
          })
        } catch (error) {
          container.textContent = content
          container.style.color = '#cc0000'
        }
        return container
      },
      renderMathInline: (content) => {
        const container = document.createElement('span')
        try {
          katex.render(content, container, {
            displayMode: false,
            throwOnError: false,
          })
        } catch (error) {
          container.textContent = content
          container.style.color = '#cc0000'
        }
        return container
      },
    }),
  ],
})

MathJax Integration

Alternatively, use MathJax:
import { defineMath } from '@prosekit/extensions'

const editor = createEditor({
  extensions: [
    defineMath({
      renderMathBlock: (content) => {
        const div = document.createElement('div')
        div.innerHTML = `\\[${content}\\]`
        
        // Trigger MathJax rendering
        if (window.MathJax) {
          window.MathJax.typesetPromise([div])
        }
        
        return div
      },
      renderMathInline: (content) => {
        const span = document.createElement('span')
        span.innerHTML = `\\(${content}\\)`
        
        if (window.MathJax) {
          window.MathJax.typesetPromise([span])
        }
        
        return span
      },
    }),
  ],
})

Input Rules

The extension includes automatic input rules:

Inline Math Input Rule

Triggers when you type $...$:
import { defineMathInlineInputRule } from '@prosekit/extensions'

Block Math Enter Rule

Triggers when you press Enter after typing $$:
import { defineMathBlockEnterRule } from '@prosekit/extensions'

Editing Math

Math nodes use custom node views that allow editing:
  1. Click on a math expression to select it
  2. Press Enter or click again to edit
  3. Type your LaTeX syntax
  4. Click outside or press Escape to finish editing

Styling

Customize the appearance of math expressions:
/* Inline math */
.ProseMirror .math-inline {
  display: inline-block;
  padding: 0 0.2em;
  background-color: #f5f5f5;
  border-radius: 3px;
}

/* Block math */
.ProseMirror .math-block {
  margin: 1em 0;
  padding: 1em;
  background-color: #f5f5f5;
  border-radius: 4px;
  overflow-x: auto;
}

/* Error state */
.ProseMirror .math-error {
  color: #cc0000;
  background-color: #ffe0e0;
}

Common LaTeX Examples

Inline Math

$x^2$                    // Superscript
$x_i$                    // Subscript
$\frac{a}{b}$           // Fraction
$\sqrt{x}$              // Square root
$\sum_{i=1}^{n}$        // Summation

Block Math

// Quadratic formula
$$
x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
$$

// Matrix
$$
\begin{bmatrix}
a & b \\
c & d
\end{bmatrix}
$$

// Integral
$$
\int_0^\infty f(x) \, dx
$$

// System of equations
$$
\begin{cases}
x + y = 5 \\
2x - y = 1
\end{cases}
$$

Error Handling

Handle LaTeX syntax errors gracefully:
renderMathBlock: (content) => {
  const div = document.createElement('div')
  
  try {
    katex.render(content, div, {
      displayMode: true,
      throwOnError: true, // Throw on error
    })
  } catch (error) {
    // Display error message
    div.className = 'math-error'
    div.textContent = `Error: ${error.message}`
  }
  
  return div
}

Advanced Configuration

Custom Macros

renderMathBlock: (content) => {
  const div = document.createElement('div')
  katex.render(content, div, {
    displayMode: true,
    macros: {
      '\\RR': '\\mathbb{R}',     // Real numbers
      '\\NN': '\\mathbb{N}',     // Natural numbers
      '\\ZZ': '\\mathbb{Z}',     // Integers
      '\\QQ': '\\mathbb{Q}',     // Rationals
      '\\CC': '\\mathbb{C}',     // Complex numbers
    },
  })
  return div
}

Trust Settings

Control what LaTeX commands are allowed:
katex.render(content, element, {
  trust: (context) => {
    // Allow specific commands
    return ['\\href', '\\url', '\\includegraphics'].includes(context.command)
  },
})

Complete Example

import { createEditor } from '@prosekit/core'
import { defineMath, defineMathPlugin } from '@prosekit/extensions'
import katex from 'katex'
import 'katex/dist/katex.min.css'

function createMathEditor() {
  const editor = createEditor({
    extensions: [
      defineMath({
        renderMathBlock: (content) => {
          const div = document.createElement('div')
          div.className = 'math-block'
          
          try {
            katex.render(content, div, {
              displayMode: true,
              throwOnError: false,
              errorColor: '#cc0000',
              macros: {
                '\\RR': '\\mathbb{R}',
              },
            })
          } catch (error) {
            div.className = 'math-block math-error'
            div.textContent = `LaTeX Error: ${error.message}`
          }
          
          return div
        },
        renderMathInline: (content) => {
          const span = document.createElement('span')
          span.className = 'math-inline'
          
          try {
            katex.render(content, span, {
              displayMode: false,
              throwOnError: false,
            })
          } catch (error) {
            span.className = 'math-inline math-error'
            span.textContent = content
          }
          
          return span
        },
      }),
      defineMathPlugin(),
    ],
  })
  
  return editor
}

const editor = createMathEditor()
  • Use with defineCodeBlock() for technical documentation
  • Combine with defineList() for numbered equations
  • Works within defineTable() cells for equation arrays