Skip to content

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.

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.

Click to try it out

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>',
});
PropertyValue
ProseMirror nameheading
TypeNode
Groupblock
Contentinline* (zero or more inline nodes)
DefiningYes
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.

OptionTypeDefaultDescription
levelsnumber[][1, 2, 3, 4]Which heading levels to allow
HTMLAttributesRecord<string, unknown>{}HTML attributes added to the heading element
import { Heading } from '@domternal/core';
// Only allow h1 and h2
const 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.

import { Heading } from '@domternal/core';
const CustomHeading = Heading.configure({
HTMLAttributes: { class: 'my-heading' },
});
AttributeTypeDefaultDescription
levelnumber1The 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.

CommandDescription
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 h2
editor.commands.setHeading({ level: 2 });
// Toggle between h1 and paragraph
editor.commands.toggleHeading({ level: 1 });
// With chaining
editor.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.

ShortcutCommand
Mod-Alt-1toggleHeading({ level: 1 })
Mod-Alt-2toggleHeading({ level: 2 })
Mod-Alt-3toggleHeading({ level: 3 })
Mod-Alt-4toggleHeading({ level: 4 })
BackspaceConvert 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.

InputResult
# + spaceHeading level 1
## + spaceHeading level 2
### + spaceHeading level 3
#### + spaceHeading 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.

Heading registers a dropdown in the toolbar with the name heading in group blocks at priority 200.

The dropdown contains:

ItemCommandIconShortcut
Normal textsetParagraphtextTMod-Alt-0
Heading 1toggleHeading({ level: 1 })textHOneMod-Alt-1
Heading 2toggleHeading({ level: 2 })textHTwoMod-Alt-2
Heading 3toggleHeading({ level: 3 })textHThreeMod-Alt-3
Heading 4toggleHeading({ level: 4 })textHFourMod-Alt-4

The dropdown uses dynamicIcon: true, which means the dropdown button icon changes to match the currently active heading level.

{
"type": "heading",
"attrs": { "level": 2 },
"content": [
{ "type": "text", "text": "Hello world" }
]
}

An empty heading:

{
"type": "heading",
"attrs": { "level": 1 }
}

@domternal/core - Heading.ts