Heading
The Heading node provides block-level heading elements (<h1> through <h4> by default). Headings support markdown-style input rules (# + space, ## + space, etc.), keyboard shortcuts, and a toolbar dropdown for switching between heading levels and normal text.
Live Playground
Section titled “Live Playground”Type # + space, ## + space, ### + space, or #### + space at the start of a line to create headings. Use Mod-Alt-1 through Mod-Alt-4 to toggle heading levels.
Vanilla JS preview · Angular components produce the same output
Vanilla JS preview · React components produce the same output
The buttons above the editor are custom HTML buttons wired to toggleHeading() and setParagraph(). Active state is tracked with editor.isActive().
Heading is included in StarterKit. If you are building a custom setup without StarterKit, add it manually:
import { Editor, Document, Text, Paragraph, Heading } from '@domternal/core';
const editor = new Editor({ element: document.getElementById('editor')!, extensions: [Document, Text, Paragraph, Heading], content: '<h1>Hello world</h1><p>Some text</p>',});import { Component, signal } from '@angular/core';import { DomternalEditorComponent } from '@domternal/angular';import { Editor, Document, Text, Paragraph, Heading } from '@domternal/core';
@Component({ selector: 'app-editor', imports: [DomternalEditorComponent], template: ` <domternal-editor [extensions]="extensions" [content]="content" (editorCreated)="editor.set($event)" /> `,})export class EditorComponent { editor = signal<Editor | null>(null); extensions = [Document, Text, Paragraph, Heading]; content = '<h1>Hello world</h1><p>Some text</p>';}import { Domternal } from '@domternal/react';import { Document, Text, Paragraph, Heading } from '@domternal/core';
export default function Editor() { return ( <Domternal extensions={[Document, Text, Paragraph, Heading]} content="<h1>Hello world</h1><p>Some text</p>" > <Domternal.Content /> </Domternal> );}Schema
Section titled “Schema”| Property | Value |
|---|---|
| ProseMirror name | heading |
| Type | Node |
| Group | block |
| Content | inline* (zero or more inline nodes) |
| Defining | Yes |
| HTML tag | <h1> through <h4> (based on level) |
The defining property means that when you select a heading and paste content over it, the replacement keeps the heading type rather than converting to the pasted node type.
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
levels | number[] | [1, 2, 3, 4] | Which heading levels to allow |
HTMLAttributes | Record<string, unknown> | {} | HTML attributes added to the heading element |
Custom heading levels
Section titled “Custom heading levels”import { Heading } from '@domternal/core';
// Only allow h1 and h2const CustomHeading = Heading.configure({ levels: [1, 2],});This restricts both the input rules and keyboard shortcuts to only the configured levels. For example, ### + space would not trigger a heading conversion if level 3 is not included.
Custom HTML attributes
Section titled “Custom HTML attributes”import { Heading } from '@domternal/core';
const CustomHeading = Heading.configure({ HTMLAttributes: { class: 'my-heading' },});Attributes
Section titled “Attributes”| Attribute | Type | Default | Description |
|---|---|---|---|
level | number | 1 | The heading level (1-4) |
The level attribute is parsed from the HTML tag name (<h1> = level 1, <h2> = level 2, etc.) and is not rendered as an HTML attribute. Instead, it determines which tag is used in the output.
Commands
Section titled “Commands”| Command | Description |
|---|---|
setHeading({ level }) | Convert the current block to a heading at the given level |
toggleHeading({ level }) | Toggle between heading and paragraph |
// Convert the current block to an h2editor.commands.setHeading({ level: 2 });
// Toggle between h1 and paragrapheditor.commands.toggleHeading({ level: 1 });
// With chainingeditor.chain().focus().toggleHeading({ level: 3 }).run();setHeading returns false if the requested level is not in the configured levels array. toggleHeading converts a heading back to a paragraph if the current block is already a heading at that level.
Keyboard shortcuts
Section titled “Keyboard shortcuts”| Shortcut | Command |
|---|---|
Mod-Alt-1 | toggleHeading({ level: 1 }) |
Mod-Alt-2 | toggleHeading({ level: 2 }) |
Mod-Alt-3 | toggleHeading({ level: 3 }) |
Mod-Alt-4 | toggleHeading({ level: 4 }) |
Backspace | Convert heading to paragraph when cursor is at the start |
Shortcuts are generated dynamically from the levels option. If you configure levels: [1, 2], only Mod-Alt-1 and Mod-Alt-2 are registered.
The Backspace behavior converts a heading to a paragraph when the cursor is at position 0 inside the heading and the selection is empty. This lets users easily “undo” a heading by pressing backspace at the beginning of the line.
Input rules
Section titled “Input rules”| Input | Result |
|---|---|
# + space | Heading level 1 |
## + space | Heading level 2 |
### + space | Heading level 3 |
#### + space | Heading level 4 |
Type the hash characters at the start of a new line, then press space. The line converts to a heading at the matching level. Only levels included in the levels option are recognized.
Toolbar items
Section titled “Toolbar items”Heading registers a dropdown in the toolbar with the name heading in group blocks at priority 200.
The dropdown contains:
| Item | Command | Icon | Shortcut |
|---|---|---|---|
| Normal text | setParagraph | textT | Mod-Alt-0 |
| Heading 1 | toggleHeading({ level: 1 }) | textHOne | Mod-Alt-1 |
| Heading 2 | toggleHeading({ level: 2 }) | textHTwo | Mod-Alt-2 |
| Heading 3 | toggleHeading({ level: 3 }) | textHThree | Mod-Alt-3 |
| Heading 4 | toggleHeading({ level: 4 }) | textHFour | Mod-Alt-4 |
The dropdown uses dynamicIcon: true, which means the dropdown button icon changes to match the currently active heading level.
JSON representation
Section titled “JSON representation”{ "type": "heading", "attrs": { "level": 2 }, "content": [ { "type": "text", "text": "Hello world" } ]}An empty heading:
{ "type": "heading", "attrs": { "level": 1 }}Source
Section titled “Source”@domternal/core - Heading.ts