Skip to main content

Overview

The heading extension adds support for heading nodes with levels 1-6, corresponding to HTML <h1> through <h6> elements. Headings are essential for document structure and hierarchy.

Installation

npm install @prosekit/extensions

Usage

import { defineHeading } from '@prosekit/extensions/heading'
import { createEditor } from '@prosekit/core'

const extension = defineHeading()
const editor = createEditor({ extension })

API Reference

defineHeading()

Defines a heading node with commands, keymap, input rules, and node specification. Returns: HeadingExtension This extension includes:
  • Node specification with level attribute
  • Commands for manipulating headings
  • Keyboard shortcuts for levels 1-6
  • Input rules for Markdown-style syntax

defineHeadingSpec()

Defines only the heading node specification. Returns: HeadingSpecExtension

defineHeadingCommands()

Defines heading-related commands. Returns: HeadingCommandsExtension

defineHeadingKeymap()

Defines keyboard shortcuts for heading operations. Returns: PlainExtension

defineHeadingInputRule()

Defines input rules for Markdown-style heading syntax. Returns: PlainExtension

Node Specification

The heading node is defined with the following properties:
  • name: heading
  • content: inline* (accepts zero or more inline nodes)
  • group: block
  • defining: true (prevents wrapping)
  • attrs:
    • level: number (1-6, default: 1)
  • parseDOM: Parses <h1> through <h6> tags
  • toDOM: Renders as appropriate <h1>-<h6> element based on level

Types

HeadingAttrs

interface HeadingAttrs {
  level: number // 1-6
}

Commands

setHeading(attrs?)

Converts the current block to a heading. Parameters:
  • attrs?: HeadingAttrs - Optional attributes. If omitted, defaults to level 1.
import { useEditor } from '@prosekit/react'

function HeadingButton() {
  const editor = useEditor()
  
  return (
    <button onClick={() => editor.commands.setHeading({ level: 2 })}>
      Heading 2
    </button>
  )
}

insertHeading(attrs?)

Inserts a new heading node. Parameters:
  • attrs?: HeadingAttrs - Optional attributes. If omitted, defaults to level 1.
editor.commands.insertHeading({ level: 3 })

toggleHeading(attrs?)

Toggles between a heading and paragraph. Parameters:
  • attrs?: HeadingAttrs - Optional attributes. If omitted, defaults to level 1.
editor.commands.toggleHeading({ level: 1 })

Keyboard Shortcuts

ShortcutAction
Mod-Alt-1Toggle heading level 1
Mod-Alt-2Toggle heading level 2
Mod-Alt-3Toggle heading level 3
Mod-Alt-4Toggle heading level 4
Mod-Alt-5Toggle heading level 5
Mod-Alt-6Toggle heading level 6
BackspaceConvert to paragraph when at start of heading
Note: Mod is Cmd on macOS and Ctrl on Windows/Linux.

Input Rules

The extension supports Markdown-style heading syntax:
# + space → Heading 1
## + space → Heading 2
### + space → Heading 3
#### + space → Heading 4
##### + space → Heading 5
###### + space → Heading 6
Type 1-6 hash symbols followed by a space at the start of a line to convert it to the corresponding heading level.

Examples

Basic Setup

import { createEditor } from '@prosekit/core'
import { union } from '@prosekit/core'
import { defineDoc } from '@prosekit/extensions/doc'
import { defineParagraph } from '@prosekit/extensions/paragraph'
import { defineHeading } from '@prosekit/extensions/heading'
import { defineText } from '@prosekit/extensions/text'

const extension = union([
  defineDoc(),
  defineParagraph(),
  defineHeading(),
  defineText(),
])

const editor = createEditor({ extension })

Heading Toolbar

import { useEditor } from '@prosekit/react'
import { Button } from './ui/button'

function HeadingToolbar() {
  const editor = useEditor()
  
  const headingLevels = [1, 2, 3, 4, 5, 6] as const
  
  return (
    <div className="toolbar">
      {headingLevels.map((level) => {
        const isActive = editor.nodes.heading.isActive({ level })
        
        return (
          <Button
            key={level}
            onClick={() => editor.commands.toggleHeading({ level })}
            data-active={isActive}
          >
            H{level}
          </Button>
        )
      })}
    </div>
  )
}
import { useEditor } from '@prosekit/react'

function HeadingSelect() {
  const editor = useEditor()
  
  const getCurrentLevel = () => {
    const { level } = editor.nodes.heading.attrs() || {}
    return level || 0
  }
  
  const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    const level = parseInt(e.target.value)
    if (level === 0) {
      editor.commands.setParagraph()
    } else {
      editor.commands.setHeading({ level })
    }
  }
  
  return (
    <select value={getCurrentLevel()} onChange={handleChange}>
      <option value={0}>Normal</option>
      <option value={1}>Heading 1</option>
      <option value={2}>Heading 2</option>
      <option value={3}>Heading 3</option>
      <option value={4}>Heading 4</option>
      <option value={5}>Heading 5</option>
      <option value={6}>Heading 6</option>
    </select>
  )
}

Check Active Heading Level

const editor = useEditor()

// Check if any heading is active
const isHeading = editor.nodes.heading.isActive()

// Check if specific level is active
const isH1 = editor.nodes.heading.isActive({ level: 1 })
const isH2 = editor.nodes.heading.isActive({ level: 2 })

// Get current heading attributes
const attrs = editor.nodes.heading.attrs()
if (attrs) {
  console.log('Current heading level:', attrs.level)
}

Styling

Heading nodes render as standard HTML heading elements:
.prosekit-editor h1 {
  font-size: 2em;
  font-weight: bold;
  margin: 0.67em 0;
}

.prosekit-editor h2 {
  font-size: 1.5em;
  font-weight: bold;
  margin: 0.75em 0;
}

.prosekit-editor h3 {
  font-size: 1.17em;
  font-weight: bold;
  margin: 0.83em 0;
}

/* ... and so on for h4, h5, h6 */

HTML Structure

Input HTML:
<h1>Level 1 Heading</h1>
<h2>Level 2 Heading</h2>
<h3>Level 3 Heading</h3>
Output HTML:
<h1>Level 1 Heading</h1>
<h2>Level 2 Heading</h2>
<h3>Level 3 Heading</h3>

Accessibility

Headings are crucial for document accessibility:
  • Use headings to create a logical document outline
  • Don’t skip heading levels (e.g., don’t go from h1 to h3)
  • Use only one h1 per page for the main title
  • Screen readers use headings for navigation