Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e3f182a
Added `renderPreview` field to code block options and added LaTeX pre…
matthewlipski Jun 9, 2026
6ccc955
Math block overhaul
matthewlipski Jun 16, 2026
ae49edc
Small fix
matthewlipski Jun 16, 2026
9687742
Implemented minor CodeRabbit feedback
matthewlipski Jun 16, 2026
9626d7e
Updated test snapshot
matthewlipski Jun 16, 2026
de7b1df
Big update to math block
matthewlipski Jun 18, 2026
719fd7a
Merge branch 'main' into code-block-previews
matthewlipski Jun 18, 2026
ed8c681
Major changes
matthewlipski Jun 23, 2026
c8388cc
Fixed build issue
matthewlipski Jun 23, 2026
f2bfe62
Reverted `inert`
matthewlipski Jun 23, 2026
57c459a
Implemented PR feedback
matthewlipski Jun 23, 2026
3a1e4ca
Fixed test editor having unsupported default language for code block
matthewlipski Jun 23, 2026
ed8e6c1
Fixed export error
matthewlipski Jun 23, 2026
ce99770
Removed FloatingUI positioning from math block popup
matthewlipski Jun 24, 2026
d6d1d46
Moved keyboard & selection handling to extension
matthewlipski Jun 24, 2026
aa666bc
Added React math block
matthewlipski Jun 25, 2026
d1b52d2
feat: Math inline content (#2878)
matthewlipski Jun 30, 2026
98eddb3
Refactored math block/inline content to have the same structure
matthewlipski Jun 30, 2026
2d53854
Small fixes
matthewlipski Jun 30, 2026
3e18e85
Added input rules and removed old katex dep from code block
matthewlipski Jun 30, 2026
f5ba0b1
Changed `Prosemirror-selectednode` element
matthewlipski Jul 1, 2026
e560634
Added exporters
matthewlipski Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 37 additions & 11 deletions docs/content/docs/features/blocks/code-blocks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ type CodeBlockOptions = {
aliases?: string[];
}
>;
createHighlighter?: () => Promise<HighlighterGeneric<any, any>>;
};
```

Expand All @@ -44,15 +43,38 @@ type CodeBlockOptions = {

`supportedLanguages:` The syntax highlighting languages supported by the code block, which is an empty array by default.

`createHighlighter:` The [Shiki highliter](https://shiki.style/guide/load-theme) to use for syntax highlighting.
**Syntax Highlighting**

BlockNote also provides a generic set of options for syntax highlighting in the `@blocknote/code-block` package, which support a wide range of languages:
Syntax highlighting is handled by a separate editor extension, configured at the editor level via the `syntaxHighlighting` option (not on the code block itself), so it can highlight any block's content:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious if we can find a better way to do this. This will make it difficult to create easily installable block-plugins later I think

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think syntaxHighlighting should be a top-level option like this. But as a single extension, maybe yea.
What's annoying is that it wants to be a singleton, but multiple places need to configure it. I'll think on it.


```ts
import { createCodeBlockSpec } from "@blocknote/core";
import { codeBlockOptions } from "@blocknote/code-block";
type SyntaxHighlightingOptions = {
createHighlighter?: () => Promise<HighlighterGeneric<any, any>>;
highlightBlock?: (block: {
type: string;
props: Record<string, any>;
}) => string | undefined;
};
```

`createHighlighter:` The [Shiki highlighter](https://shiki.style/guide/load-theme) to use for syntax highlighting.

const codeBlock = createCodeBlockSpec(codeBlockOptions);
`highlightBlock:` Picks the language to highlight a block's content as (return the language key, or `undefined` to leave it un-highlighted). This is how you enable highlighting for specific blocks. Defaults to the block's `language` prop (`(block) => block.props.language`), which covers the code block. For a block with a fixed language, return it directly — e.g. for a math block: `(block) => (block.type === "math" ? "latex" : block.props.language)`.

BlockNote provides a generic, ready-to-use set of these in the `@blocknote/code-block` package, which supports a wide range of languages. The code block options and the highlighter are exported separately:

```ts
import { createCodeBlockSpec } from "@blocknote/core";
import { codeBlockOptions, createHighlighter } from "@blocknote/code-block";

const editor = useCreateBlockNote({
syntaxHighlighting: { createHighlighter },
schema: BlockNoteSchema.create().extend({
blockSpecs: {
codeBlock: createCodeBlockSpec(codeBlockOptions),
},
}),
});
```

See [this example](/examples/theming/code-block) to see it in action.
Expand Down Expand Up @@ -92,6 +114,15 @@ import { createHighlighter } from "./shiki.bundle.js";

export default function App() {
const editor = useCreateBlockNote({
// The highlighter is configured at the editor level, separately from the
// code block's own options.
syntaxHighlighting: {
createHighlighter: () =>
createHighlighter({
themes: ["light-plus", "dark-plus"],
langs: [],
}),
},
schema: BlockNoteSchema.create().extend({
blockSpecs: {
codeBlock: createCodeBlockSpec({
Expand All @@ -103,11 +134,6 @@ export default function App() {
aliases: ["ts"],
},
},
createHighlighter: () =>
createHighlighter({
themes: ["light-plus", "dark-plus"],
langs: [],
}),
}),
},
}),
Expand Down
3 changes: 2 additions & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@
"tailwind-merge": "^3.4.0",
"y-partykit": "^0.0.25",
"yjs": "^13.6.27",
"zod": "^4.3.5"
"zod": "^4.3.5",
"@blocknote/math-block": "workspace:*"
},
"devDependencies": {
"@blocknote/code-block": "workspace:*",
Expand Down
5 changes: 4 additions & 1 deletion examples/04-theming/06-code-block/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import { useCreateBlockNote } from "@blocknote/react";
// This packages some of the most used languages in on-demand bundle
import { codeBlockOptions } from "@blocknote/code-block";
import { codeBlockOptions, createHighlighter } from "@blocknote/code-block";

export default function App() {
// Creates a new editor instance.
const editor = useCreateBlockNote({
// The Shiki highlighter is configured at the editor level, separately from
// the code block's own options (default language & language menu).
syntaxHighlighting: { createHighlighter },
schema: BlockNoteSchema.create().extend({
blockSpecs: {
codeBlock: createCodeBlockSpec(codeBlockOptions),
Expand Down
16 changes: 10 additions & 6 deletions examples/04-theming/07-custom-code-block/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ import { createHighlighter } from "./shiki.bundle";
export default function App() {
// Creates a new editor instance.
const editor = useCreateBlockNote({
// The Shiki highlighter is configured at the editor level, separately from
// the code block's own options (default language & language menu).
syntaxHighlighting: {
// This creates a highlighter, it can be asynchronous to load it afterwards
createHighlighter: () =>
createHighlighter({
themes: ["dark-plus", "light-plus"],
langs: [],
}),
},
schema: BlockNoteSchema.create().extend({
blockSpecs: {
codeBlock: createCodeBlockSpec({
Expand All @@ -27,12 +37,6 @@ export default function App() {
name: "Vue",
},
},
// This creates a highlighter, it can be asynchronous to load it afterwards
createHighlighter: () =>
createHighlighter({
themes: ["dark-plus", "light-plus"],
langs: [],
}),
}),
},
}),
Expand Down
17 changes: 17 additions & 0 deletions examples/06-custom-schema/09-math-block/.bnexample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"playground": true,
"docs": true,
"author": "matthewlipski",
"tags": [
"Intermediate",
"Blocks",
"Custom Schemas",
"Suggestion Menus",
"Slash Menu"
],
"dependencies": {
"@blocknote/code-block": "latest",
"@blocknote/math-block": "latest",
"react-icons": "^5.5.0"
}
}
10 changes: 10 additions & 0 deletions examples/06-custom-schema/09-math-block/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Math Block

In this example, we register the `@blocknote/math-block` block in a custom schema. The math block renders LaTeX as MathML (using Temml) for the browser to display natively, and reveals an editable LaTeX source popup when selected. Exporting to HTML produces a MathML `<math>` element, and pasting MathML back in is converted to LaTeX.

**Try it out:** Click a formula to edit its LaTeX!

**Relevant Docs:**

- [Custom Blocks](/docs/features/custom-schemas/custom-blocks)
- [Editor Setup](/docs/getting-started/editor-setup)
14 changes: 14 additions & 0 deletions examples/06-custom-schema/09-math-block/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html lang="en">
Comment thread
matthewlipski marked this conversation as resolved.
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Math Block</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>
11 changes: 11 additions & 0 deletions examples/06-custom-schema/09-math-block/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./src/App.jsx";
Comment thread
matthewlipski marked this conversation as resolved.

const root = createRoot(document.getElementById("root")!);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
33 changes: 33 additions & 0 deletions examples/06-custom-schema/09-math-block/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@blocknote/example-custom-schema-math-block",
"description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
"type": "module",
"private": true,
"version": "0.12.4",
"scripts": {
"start": "vp dev",
"dev": "vp dev",
"build:prod": "tsc && vp build",
"preview": "vp preview"
},
"dependencies": {
"@blocknote/ariakit": "latest",
"@blocknote/core": "latest",
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
"@mantine/core": "^9.0.2",
"@mantine/hooks": "^9.0.2",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"@blocknote/code-block": "latest",
"@blocknote/math-block": "latest",
"react-icons": "^5.5.0"
},
"devDependencies": {
"@types/react": "^19.2.3",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"vite-plus": "catalog:"
}
}
128 changes: 128 additions & 0 deletions examples/06-custom-schema/09-math-block/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import "@blocknote/core/fonts/inter.css";
import { BlockNoteSchema } from "@blocknote/core";
import {
filterSuggestionItems,
insertOrUpdateBlockForSlashMenu,
} from "@blocknote/core/extensions";
import { createHighlighter } from "@blocknote/code-block";
import {
createReactInlineMathSpec,
createReactMathBlockSpec,
} from "@blocknote/math-block";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import {
getDefaultReactSlashMenuItems,
SuggestionMenuController,
useCreateBlockNote,
} from "@blocknote/react";
import { TbMathFunction } from "react-icons/tb";

// Our schema with block specs, which contain the configs and implementations for blocks
// that we want our editor to use.
const schema = BlockNoteSchema.create().extend({
blockSpecs: {
// Creates an instance of the Math block and adds it to the schema.
math: createReactMathBlockSpec(),
},
inlineContentSpecs: {
// Creates an instance of the inline Math content and adds it to the schema.
inlineMath: createReactInlineMathSpec(),
},
});

// Slash menu item to insert a Math block.
const insertMath = (editor: typeof schema.BlockNoteEditor) => ({
title: "Math Block",
subtext: "Insert a LaTeX math formula",
onItemClick: () =>
insertOrUpdateBlockForSlashMenu(editor, {
type: "math",
}),
aliases: ["math", "latex", "formula", "equation"],
group: "Advanced",
icon: <TbMathFunction />,
});

// Slash menu item to insert an inline Math equation.
const insertInlineMath = (editor: typeof schema.BlockNoteEditor) => ({
title: "Inline Equation",
subtext: "Insert an inline LaTeX equation",
onItemClick: () => {
editor.insertInlineContent([
{ type: "inlineMath", content: "" },
// Adds a trailing space so the cursor can leave the equation.
" ",
]);
},
aliases: ["math", "latex", "formula", "equation"],
group: "Advanced",
icon: <TbMathFunction />,
});

export default function App() {
const editor = useCreateBlockNote({
// Configures the syntax highlighting extension to always use LaTeX syntax highlighting in the
// Math block.
syntaxHighlighting: {
createHighlighter,
highlightBlock: (block) =>
block.type === "math" ? "latex" : block.props.language,
},
schema,
initialContent: [
{
type: "paragraph",
content: "Click a formula to edit its LaTeX source:",
},
{
type: "math",
content: "a^2 = \\sqrt{b^2 + c^2}",
},
{
type: "math",
content: "\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}",
},
{
type: "paragraph",
content: [
"Equations can also be inline, like ",
{ type: "inlineMath", content: "e^{i\\pi} + 1 = 0" },
". Click one to edit its LaTeX source.",
],
},
{
type: "paragraph",
content: "Press the '/' key to open the Slash Menu and add another",
},
],
});

// Renders the editor instance using a React component.
return (
<BlockNoteView editor={editor} slashMenu={false}>
{/* Replaces the default Slash Menu. */}
<SuggestionMenuController
triggerCharacter={"/"}
getItems={async (query) => {
// Gets all default slash menu items.
const defaultItems = getDefaultReactSlashMenuItems(editor);
// Finds index of last item in "Advanced" group.
const lastAdvancedIndex = defaultItems.findLastIndex(
(item) => item.group === "Advanced",
);
// Inserts the Math items at the end of the "Advanced" group.
defaultItems.splice(
lastAdvancedIndex + 1,
0,
insertMath(editor),
insertInlineMath(editor),
);

// Returns filtered items based on the query.
return filterSuggestionItems(defaultItems, query);
}}
/>
</BlockNoteView>
);
}
29 changes: 29 additions & 0 deletions examples/06-custom-schema/09-math-block/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"composite": true
},
"include": ["."],
"__ADD_FOR_LOCAL_DEV_references": [
{
"path": "../../../packages/core/"
},
{
"path": "../../../packages/react/"
}
]
}
1 change: 1 addition & 0 deletions examples/06-custom-schema/09-math-block/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite-plus/client" />
Loading
Loading