Enter rules allow you to perform special actions when the user presses Enter and the text before the cursor matches a specific pattern. They’re useful for creating smart list behaviors, breaking out of blocks, or inserting special content.
Overview
Enter rules are triggered when:
The user presses the Enter key
The text directly before the cursor matches the rule’s regex pattern
The regex pattern must end with $
ProseKit provides two types of enter rules:
Custom Enter Rules : Define fully custom behavior
Text Block Enter Rules : Replace matched text with a block node
Basic Usage
import { defineEnterRule } from '@prosekit/extensions/enter-rule'
const extension = defineEnterRule ({
regex: / ^ - $ / ,
handler : ({ state , dispatch , utils }) => {
// Delete the matched text and insert a list item
if ( dispatch ) {
const tr = state . tr
const { $from } = state . selection
const start = $from . start ()
tr . delete ( start , $from . pos )
tr . setBlockType ( start , start , state . schema . nodes . listItem )
dispatch ( tr )
}
return true
},
})
Text Block Enter Rules
Replace the matched text with a block node when Enter is pressed.
import { defineTextBlockEnterRule } from '@prosekit/extensions/enter-rule'
const extension = defineTextBlockEnterRule ({
regex: / ^ --- $ / ,
type: 'horizontalRule' ,
})
This will convert a line containing --- into a horizontal rule when the user presses Enter.
API Reference
defineEnterRule
The regular expression to match against the text before the cursor. Must end with $.
Function called when the pattern matches and Enter is pressed. type EnterRuleHandler = ( options : {
state : EditorState
dispatch ?: ( tr : Transaction ) => void
utils : {
canSplit : boolean
matchStart : number
matchEnd : number
}
}) => boolean
Return true to indicate the rule handled the event, false to let other handlers process it.
defineTextBlockEnterRule
The regular expression to match against. Must end with $.
type
string | NodeType
required
The node type to insert when the pattern matches.
attrs
Attrs | null | ((match: RegExpMatchArray) => Attrs | null)
Attributes to set on the new node.
Real-World Examples
Exit Empty List Items
import { defineEnterRule } from '@prosekit/extensions/enter-rule'
const exitListExtension = defineEnterRule ({
regex: / ^$ / ,
handler : ({ state , dispatch , utils }) => {
const { $from } = state . selection
// Check if we're in an empty list item
if ( $from . parent . type . name !== 'listItem' ) {
return false
}
if ( $from . parent . content . size > 0 ) {
return false
}
// Convert empty list item to paragraph
if ( dispatch ) {
const tr = state . tr
const pos = $from . before ( $from . depth )
tr . setBlockType ( pos , pos , state . schema . nodes . paragraph )
dispatch ( tr )
}
return true
},
})
Insert Horizontal Rule
import { defineTextBlockEnterRule } from '@prosekit/extensions/enter-rule'
const horizontalRuleExtension = defineTextBlockEnterRule ({
regex: / ^ --- $ / ,
type: 'horizontalRule' ,
})
Type --- and press Enter to insert a horizontal rule.
Smart Code Block Exit
import { defineEnterRule } from '@prosekit/extensions/enter-rule'
const exitCodeBlockExtension = defineEnterRule ({
regex: / ^ ``` $ / ,
handler : ({ state , dispatch }) => {
const { $from } = state . selection
// Only trigger in code blocks
if ( $from . parent . type . name !== 'codeBlock' ) {
return false
}
if ( dispatch ) {
const tr = state . tr
// Exit the code block and insert a paragraph
const after = $from . after ( $from . depth - 1 )
tr . insert ( after , state . schema . nodes . paragraph . create ())
tr . setSelection (
TextSelection . near ( tr . doc . resolve ( after + 1 ))
)
dispatch ( tr )
}
return true
},
})
Break Out of Blockquote
import { defineEnterRule } from '@prosekit/extensions/enter-rule'
const exitBlockquoteExtension = defineEnterRule ({
regex: / ^$ / ,
handler : ({ state , dispatch }) => {
const { $from } = state . selection
// Check if we're in an empty paragraph inside a blockquote
if ( $from . parent . type . name !== 'paragraph' ) {
return false
}
if ( $from . parent . content . size > 0 ) {
return false
}
// Find the blockquote ancestor
let blockquoteDepth = - 1
for ( let d = $from . depth ; d > 0 ; d -- ) {
if ( $from . node ( d ). type . name === 'blockquote' ) {
blockquoteDepth = d
break
}
}
if ( blockquoteDepth === - 1 ) {
return false
}
// Exit the blockquote
if ( dispatch ) {
const tr = state . tr
const after = $from . after ( blockquoteDepth )
tr . insert ( after , state . schema . nodes . paragraph . create ())
tr . setSelection (
TextSelection . near ( tr . doc . resolve ( after + 1 ))
)
dispatch ( tr )
}
return true
},
})
Use Cases
Exit Empty Blocks Let users break out of lists, blockquotes, or other containers by pressing Enter in empty blocks.
Insert Special Nodes Create shortcuts for inserting horizontal rules, page breaks, or other special nodes.
Smart List Behavior Implement custom list continuation, indentation, or exit behavior.
Context-Aware Actions Perform different actions based on the current context (node type, depth, content).
Tips
Enter rules are checked in the order they’re defined. Return true from your handler to prevent subsequent rules from running.
Use the utils.canSplit property to check if the current position can be split before performing actions.
Be careful with empty patterns (/^$/). They will match every Enter press, so make sure your handler checks the context carefully before taking action.