Code Block Lowlight
CodeBlockLowlight extends the built-in CodeBlock node with syntax highlighting powered by lowlight (a lightweight highlight.js wrapper). It adds language auto-detection, tab indentation with configurable tab size, and incremental re-highlighting that only updates affected code blocks on edits. It inherits all commands, keyboard shortcuts, toolbar items, and input rules from CodeBlock.
Separate package. Install alongside the core package.
Live Playground
Section titled “Live Playground”Edit the JavaScript code below to see syntax highlighting update in real time. Try creating a new code block by typing ```css or ```python followed by a space. Use Tab to indent and Shift-Tab to outdent inside code blocks.
Installation
Section titled “Installation”pnpm add @domternal/extension-code-block-lowlight lowlightimport { Editor, Document, Paragraph, Text } from '@domternal/core';import { CodeBlockLowlight } from '@domternal/extension-code-block-lowlight';import { createLowlight, common } from 'lowlight';import '@domternal/theme';
const lowlight = createLowlight(common);
const editor = new Editor({ element: document.getElementById('editor')!, extensions: [ Document, Paragraph, Text, CodeBlockLowlight.configure({ lowlight }), ],});createLowlight(common) registers ~40 common languages. Use createLowlight(all) for all ~190 languages, or register specific ones with lowlight.register().
import { Component, signal } from '@angular/core';import { DomternalEditorComponent, DomternalToolbarComponent } from '@domternal/angular';import { Editor, Document, Paragraph, Text } from '@domternal/core';import { CodeBlockLowlight } from '@domternal/extension-code-block-lowlight';import { createLowlight, common } from 'lowlight';
const lowlight = createLowlight(common);
@Component({ selector: 'app-editor', imports: [DomternalEditorComponent, DomternalToolbarComponent], templateUrl: './editor.html',})export class EditorComponent { editor = signal<Editor | null>(null); extensions = [ Document, Paragraph, Text, CodeBlockLowlight.configure({ lowlight }), ];}@if (editor(); as ed) { <domternal-toolbar [editor]="ed" />}<domternal-editor [extensions]="extensions" (editorCreated)="editor.set($event)"/>import { Domternal } from '@domternal/react';import { Document, Paragraph, Text } from '@domternal/core';import { CodeBlockLowlight } from '@domternal/extension-code-block-lowlight';import { createLowlight, common } from 'lowlight';
const lowlight = createLowlight(common);
export default function Editor() { return ( <Domternal extensions={[ Document, Paragraph, Text, CodeBlockLowlight.configure({ lowlight }), ]} > <Domternal.Toolbar /> <Domternal.Content /> </Domternal> );}import { Editor, Document, Paragraph, Text } from '@domternal/core';import { CodeBlockLowlight } from '@domternal/extension-code-block-lowlight';import { createLowlight, common } from 'lowlight';
const lowlight = createLowlight(common);
const editor = new Editor({ element: document.getElementById('editor')!, extensions: [ Document, Paragraph, Text, CodeBlockLowlight.configure({ lowlight, defaultLanguage: 'javascript', autoDetect: true, tabIndentation: true, tabSize: 4, }), ],});
// List registered languagesconsole.log(editor.storage.codeBlockLowlight.listLanguages());
// Toggle code blockeditor.commands.toggleCodeBlock({ language: 'typescript' });Options
Section titled “Options”CodeBlockLowlight inherits all options from CodeBlock and adds its own:
| Option | Type | Default | Description |
|---|---|---|---|
lowlight | Lowlight | null (required) | The lowlight instance created with createLowlight(). Throws if not provided. |
defaultLanguage | string | null | null | Default language when none is specified on the code block |
autoDetect | boolean | true | Auto-detect language using lowlight.highlightAuto() when no language is set |
tabIndentation | boolean | true | Tab key inserts spaces inside code blocks |
tabSize | number | 2 | Number of spaces per tab |
Inherited from CodeBlock
Section titled “Inherited from CodeBlock”| Option | Type | Default | Description |
|---|---|---|---|
languageClassPrefix | string | 'language-' | CSS class prefix for the language attribute |
exitOnTripleEnter | boolean | true | Exit code block on three consecutive Enter presses |
HTMLAttributes | Record<string, unknown> | {} | HTML attributes for the rendered element |
Lowlight setup
Section titled “Lowlight setup”import { createLowlight, common, all } from 'lowlight';
// Option 1: Common languages (~40 languages)const lowlight = createLowlight(common);
// Option 2: All languages (~190 languages)const lowlight = createLowlight(all);
// Option 3: Specific languages onlyimport { createLowlight } from 'lowlight';import javascript from 'highlight.js/lib/languages/javascript';import typescript from 'highlight.js/lib/languages/typescript';import css from 'highlight.js/lib/languages/css';
const lowlight = createLowlight();lowlight.register('javascript', javascript);lowlight.register('typescript', typescript);lowlight.register('css', css);Commands
Section titled “Commands”CodeBlockLowlight inherits all commands from CodeBlock:
setCodeBlock
Section titled “setCodeBlock”Converts the current block to a code block with an optional language.
editor.commands.setCodeBlock({ language: 'typescript' });toggleCodeBlock
Section titled “toggleCodeBlock”Toggles between a code block and a paragraph.
editor.commands.toggleCodeBlock();editor.commands.toggleCodeBlock({ language: 'javascript' });Storage
Section titled “Storage”Access storage via editor.storage.codeBlockLowlight:
| Method | Returns | Description |
|---|---|---|
listLanguages() | string[] | List of registered language names from the lowlight instance |
const languages = editor.storage.codeBlockLowlight.listLanguages();// ['javascript', 'typescript', 'css', 'python', ...]Keyboard shortcuts
Section titled “Keyboard shortcuts”CodeBlockLowlight inherits CodeBlock shortcuts and adds tab indentation:
| Key | Command | Description |
|---|---|---|
Mod-Alt-C | toggleCodeBlock | Toggle code block (inherited) |
Enter | Exit on triple-enter | Three Enter presses exit the code block (inherited, requires exitOnTripleEnter: true) |
ArrowDown | Exit at end | Arrow down at the last line exits if no node follows (inherited) |
Tab | Insert spaces | Inserts tabSize spaces at cursor (only when tabIndentation: true) |
Shift-Tab | Remove indent | Removes up to tabSize leading spaces from the current line |
Tab indentation
Section titled “Tab indentation”When tabIndentation: true (default), Tab inserts spaces (not a tab character) and Shift-Tab removes leading spaces. The number of spaces is controlled by tabSize (default: 2).
Shift-Tab only removes spaces from the beginning of the current line. It counts leading spaces and removes up to tabSize of them. If the line has no leading spaces, it does nothing.
Input rules
Section titled “Input rules”Inherited from CodeBlock:
| Input | Result |
|---|---|
```language | Creates a code block with the specified language |
``` | Creates a code block with no language (auto-detect if enabled) |
Toolbar items
Section titled “Toolbar items”Inherited from CodeBlock:
| Button | Icon | Group | Priority | Shortcut |
|---|---|---|---|---|
codeBlock | codeBlock | blocks | 140 | Mod-Alt-C |
How it works
Section titled “How it works”Extending CodeBlock
Section titled “Extending CodeBlock”CodeBlockLowlight uses CodeBlock.extend() to inherit all CodeBlock functionality (schema, commands, input rules, toolbar items) and adds:
- Syntax highlighting via the lowlight plugin
- Tab/Shift-Tab indentation shortcuts
listLanguages()storage method
Lowlight plugin
Section titled “Lowlight plugin”The lowlightPlugin creates a ProseMirror plugin with Decoration.inline decorations for syntax tokens:
- Init: Highlights all code blocks in the document on first load
- Apply: On document changes, computes affected ranges and only re-highlights code blocks that overlap with changes (incremental)
- Decorations: Returns the decoration set for the current state
Highlighting flow
Section titled “Highlighting flow”For each code block:
- Reads the
languageattribute (or falls back todefaultLanguage) - If the language is registered, calls
lowlight.highlight(language, text) - If no language and
autoDetect: true, callslowlight.highlightAuto(text) - Flattens the hast tree output into text tokens with CSS classes
- Creates
Decoration.inlinefor each token with non-empty classes
Incremental updates
Section titled “Incremental updates”The plugin avoids re-highlighting the entire document on every keystroke:
- Computes changed ranges from
tr.stepsmapped to final document coordinates - Maps existing decorations to new positions via
decorationSet.map(tr.mapping, tr.doc) - Finds code blocks that overlap with changed ranges
- Removes stale decorations for affected blocks and re-highlights only those blocks
- Blocks outside the changed ranges keep their cached decorations
Lowlight validation
Section titled “Lowlight validation”The lowlight option is required. If not provided, the plugin throws an error at initialization:
[@domternal/extension-code-block-lowlight] The "lowlight" option is required.Provide a lowlight instance: CodeBlockLowlight.configure({ lowlight: createLowlight(common) })Utility functions
Section titled “Utility functions”generateHighlightedHTML
Section titled “generateHighlightedHTML”Generates HTML with syntax-highlighted code blocks from JSON content. Unlike generateHTML() from @domternal/core, this applies lowlight highlighting to code blocks, producing <span class="hljs-keyword"> etc. inside <code> elements.
import { generateHighlightedHTML } from '@domternal/extension-code-block-lowlight';import { createLowlight, common } from 'lowlight';
const lowlight = createLowlight(common);const html = generateHighlightedHTML(json, extensions, lowlight, { defaultLanguage: 'javascript', autoDetect: false,});| Parameter | Type | Description |
|---|---|---|
content | JSONContent | The editor’s JSON content |
extensions | AnyExtension[] | Extensions used to generate HTML |
lowlight | Lowlight | A configured lowlight instance |
options | GenerateHighlightedHTMLOptions | Optional settings: defaultLanguage, autoDetect (default false), document |
createCodeHighlighter
Section titled “createCodeHighlighter”Creates a codeHighlighter callback for use with inlineStyles(). This allows server-side or email HTML rendering with syntax highlighting.
import { createLowlight, common } from 'lowlight';import { createCodeHighlighter } from '@domternal/extension-code-block-lowlight';import { inlineStyles } from '@domternal/core';
const lowlight = createLowlight(common);const styled = inlineStyles(html, { codeHighlighter: createCodeHighlighter(lowlight, { defaultLanguage: 'javascript', autoDetect: false, }),});| Parameter | Type | Description |
|---|---|---|
lowlight | Lowlight | A configured lowlight instance |
options | CreateCodeHighlighterOptions | Optional settings: defaultLanguage, autoDetect (default false) |
Returns a function (code: string, language: string | null) => string | null that highlights code and returns HTML, or null if the language is not registered and autoDetect is off.
Exports
Section titled “Exports”import { CodeBlockLowlight, lowlightPluginKey, generateHighlightedHTML, createCodeHighlighter,} from '@domternal/extension-code-block-lowlight';import type { CodeBlockLowlightOptions, CodeBlockLowlightStorage, Lowlight, GenerateHighlightedHTMLOptions, CreateCodeHighlighterOptions,} from '@domternal/extension-code-block-lowlight';| Export | Type | Description |
|---|---|---|
CodeBlockLowlight | Node (extends CodeBlock) | The code block lowlight extension |
lowlightPluginKey | PluginKey | The ProseMirror plugin key for the highlight plugin |
generateHighlightedHTML | Function | Generates HTML with syntax-highlighted code blocks from JSON content |
createCodeHighlighter | Function | Creates a codeHighlighter callback for use with inlineStyles() |
CodeBlockLowlightOptions | TypeScript type | Options for CodeBlockLowlight.configure() |
CodeBlockLowlightStorage | TypeScript type | Shape of editor.storage.codeBlockLowlight |
Lowlight | TypeScript type | The lowlight instance type (ReturnType<typeof createLowlight>) |
GenerateHighlightedHTMLOptions | TypeScript type | Options for generateHighlightedHTML() |
CreateCodeHighlighterOptions | TypeScript type | Options for createCodeHighlighter() |
Source
Section titled “Source”@domternal/extension-code-block-lowlight - CodeBlockLowlight.ts