Skip to main content

Overview

The code-block extension adds support for code block nodes with optional syntax highlighting. Code blocks preserve whitespace and disable rich text formatting, making them ideal for displaying code snippets.

Installation

npm install @prosekit/extensions

Usage

Basic Setup

import { defineCodeBlock } from '@prosekit/extensions/code-block'
import { createEditor } from '@prosekit/core'

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

With Syntax Highlighting (Shiki)

import { defineCodeBlock, defineCodeBlockShiki } from '@prosekit/extensions/code-block'
import { union } from '@prosekit/core'

const extension = union([
  defineCodeBlock(),
  defineCodeBlockShiki(),
])

API Reference

defineCodeBlock()

Defines a code block node with commands, keymap, input rules, and node specification. Returns: CodeBlockExtension This extension includes:
  • Node specification with language attribute
  • Commands for manipulating code blocks
  • Keyboard shortcuts
  • Input rules and enter rules for Markdown-style syntax

defineCodeBlockSpec()

Defines only the code block node specification. Returns: CodeBlockSpecExtension

defineCodeBlockCommands()

Defines code block-related commands. Returns: CodeBlockCommandsExtension

defineCodeBlockKeymap()

Defines keyboard shortcuts for code block operations. Returns: PlainExtension

defineCodeBlockInputRule()

Defines input rules for Markdown-style code block syntax (triple backticks). Returns: PlainExtension

defineCodeBlockEnterRule()

Defines enter key rules for creating code blocks. Returns: PlainExtension

defineCodeBlockShiki(options?)

Adds syntax highlighting support using Shiki. Parameters:
  • options?: CodeBlockHighlightOptions
Returns: PlainExtension

defineCodeBlockHighlight(options)

Custom syntax highlighting with a custom parser. Parameters:
  • options: CodeBlockHighlightOptions
Returns: PlainExtension

Node Specification

The code block node is defined with the following properties:
  • name: codeBlock
  • content: text* (plain text only)
  • group: block
  • code: true (disables rich text formatting)
  • defining: true (prevents wrapping)
  • marks: '' (no marks allowed)
  • attrs:
    • language: string (default: ”)
  • parseDOM: Parses <pre> and <code> tags
  • toDOM: Renders as <pre><code> structure

Types

CodeBlockAttrs

interface CodeBlockAttrs {
  language: string
}

CodeBlockHighlightOptions

interface CodeBlockHighlightOptions {
  parser: HighlightParser
}

type HighlightParser = (options: {
  language: string
  code: string
}) => string | null | undefined

Commands

setCodeBlock(attrs?)

Converts the current block to a code block. Parameters:
  • attrs?: CodeBlockAttrs - Optional language attribute
import { useEditor } from '@prosekit/react'

function CodeBlockButton() {
  const editor = useEditor()
  
  return (
    <button onClick={() => editor.commands.setCodeBlock({ language: 'typescript' })}>
      Code Block
    </button>
  )
}

insertCodeBlock(attrs?)

Inserts a new code block node. Parameters:
  • attrs?: CodeBlockAttrs - Optional language attribute
editor.commands.insertCodeBlock({ language: 'javascript' })

toggleCodeBlock(attrs?)

Toggles between a code block and the default block type. Parameters:
  • attrs?: CodeBlockAttrs - Optional language attribute
editor.commands.toggleCodeBlock({ language: 'python' })

setCodeBlockAttrs(attrs)

Updates the attributes of the current code block (e.g., change the language). Parameters:
  • attrs: CodeBlockAttrs - New attributes
editor.commands.setCodeBlockAttrs({ language: 'rust' })

Keyboard Shortcuts

ShortcutAction
EnterExit code block when ending with two newlines

Input Rules

The extension supports Markdown-style code block syntax:

Triple Backtick (Space)

```js + space → JavaScript code block
```python + space → Python code block
``` + space → Code block without language
Type three backticks followed by an optional language identifier and a space.

Triple Backtick (Enter)

```js + Enter → JavaScript code block
```python + Enter → Python code block
``` + Enter → Code block without language
Type three backticks followed by an optional language identifier and press Enter.

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 { defineCodeBlock } from '@prosekit/extensions/code-block'
import { defineText } from '@prosekit/extensions/text'

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

const editor = createEditor({ extension })

With Shiki Syntax Highlighting

import { defineCodeBlock, defineCodeBlockShiki } from '@prosekit/extensions/code-block'
import { union } from '@prosekit/core'

const extension = union([
  defineCodeBlock(),
  defineCodeBlockShiki(),
])

Language Selector

import { useEditor } from '@prosekit/react'
import { useState, useEffect } from 'react'

function LanguageSelect() {
  const editor = useEditor()
  const [language, setLanguage] = useState('')
  
  useEffect(() => {
    const attrs = editor.nodes.codeBlock.attrs()
    if (attrs) {
      setLanguage(attrs.language || '')
    }
  }, [editor])
  
  const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    const lang = e.target.value
    setLanguage(lang)
    editor.commands.setCodeBlockAttrs({ language: lang })
  }
  
  const isCodeBlock = editor.nodes.codeBlock.isActive()
  
  if (!isCodeBlock) return null
  
  return (
    <select value={language} onChange={handleChange}>
      <option value="">Plain Text</option>
      <option value="javascript">JavaScript</option>
      <option value="typescript">TypeScript</option>
      <option value="python">Python</option>
      <option value="rust">Rust</option>
      <option value="go">Go</option>
    </select>
  )
}

Toggle Button

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

function CodeBlockToggle() {
  const editor = useEditor()
  
  const isActive = editor.nodes.codeBlock.isActive()
  
  return (
    <Button
      onClick={() => editor.commands.toggleCodeBlock()}
      data-active={isActive}
    >
      <CodeIcon /> Code
    </Button>
  )
}

Insert with Default Language

function InsertCodeBlock() {
  const editor = useEditor()
  
  return (
    <button
      onClick={() => {
        editor.commands.insertCodeBlock({ language: 'typescript' })
      }}
    >
      Insert TypeScript Block
    </button>
  )
}

Custom Syntax Highlighter

import { defineCodeBlock, defineCodeBlockHighlight } from '@prosekit/extensions/code-block'
import { union } from '@prosekit/core'
import { customHighlighter } from './my-highlighter'

const extension = union([
  defineCodeBlock(),
  defineCodeBlockHighlight({
    parser: ({ language, code }) => {
      return customHighlighter.highlight(code, language)
    },
  }),
])

Shiki Integration

ProseKit includes built-in support for Shiki syntax highlighting:

Available Languages and Themes

import {
  shikiBundledLanguagesInfo,
  shikiBundledThemesInfo,
  type ShikiBundledLanguage,
  type ShikiBundledTheme,
} from '@prosekit/extensions/code-block'

// Get all available languages
const languages = shikiBundledLanguagesInfo

// Get all available themes
const themes = shikiBundledThemesInfo

Styling

Code blocks render with the following HTML structure:
.prosekit-editor pre {
  background: #1e1e1e;
  color: #d4d4d4;
  padding: 1rem;
  border-radius: 0.5rem;
  overflow-x: auto;
  margin: 1rem 0;
}

.prosekit-editor pre code {
  font-family: 'Fira Code', 'Monaco', 'Courier New', monospace;
  font-size: 0.875rem;
  line-height: 1.5;
}

/* Language indicator */
.prosekit-editor pre[data-language]::before {
  content: attr(data-language);
  display: block;
  font-size: 0.75rem;
  color: #888;
  margin-bottom: 0.5rem;
}

HTML Structure

Input HTML:
<pre data-language="javascript"><code class="language-javascript">const x = 42;</code></pre>
Output HTML:
<pre data-language="javascript"><code class="language-javascript">const x = 42;</code></pre>

Behavior

Exiting Code Blocks

Press Enter twice (creating two newlines) at the end of a code block to exit and create a new paragraph below.

Whitespace Preservation

All whitespace, including tabs and multiple spaces, is preserved in code blocks.

No Rich Text Formatting

Marks (bold, italic, etc.) are disabled inside code blocks to maintain plain text formatting.