Behavior Recipes
Below are some common behavior examples. You can also find a list of core behaviors on GitHub.
To add these to your editor, first import defineBehavior as well as the BehaviorPlugin.
import {defineBehavior} from '@portabletext/editor/behaviors'import {BehaviorPlugin} from '@portabletext/editor/plugins'Then, register the behavior within the EditorProvider using the BehaviorPlugin.
<EditorProvider initialConfig={{ schemaDefinition, }}> <BehaviorPlugin behaviors={[logInsertText]} /> {/* ... */}</EditorProvider>Read more about using behaviors and building your own with these guides:
Log inserted text
Section titled “Log inserted text”Send and effect type action along with a forward action to perform side effects without altering the chain of events.
const logInsertText = defineBehavior({ on: 'insert.text', actions: [ ({event}) => [ { type: 'effect', effect: () => { console.log(event) }, }, { type: 'forward', event, }, ], ],})The effect and forward actions also have shorthand functions:
const logInsertText = defineBehavior({ on: 'insert.text', actions: [ ({event}) => [ effect(() => { console.log(event) }), forward(event), ], ],})Auto-close parenthesis
Section titled “Auto-close parenthesis”You can write behaviors to auto-close common paired characters. This example closes parenthesis, and then moves the cursor in between the two characters. This logic can expand to cover more sets.
const autoCloseParens = defineBehavior({ on: 'insert.text', guard: ({snapshot, event}) => { return event.text === '(' }, actions: [ ({snapshot, event}) => [ // Execute the original event that includes the '(' {type: 'execute', event}, // Execute a new insert.text event with a closing parenthesis { type: 'execute', event: { type: 'insert.text', text: ')', }, }, // Execute a select event to move the cursor in between the parens { type: 'execute', event: { type: 'select', selection: { anchor: { path: snapshot.context.selection.anchor.path, offset: snapshot.context.selection.anchor.offset + 1, }, focus: { path: snapshot.context.selection.focus.path, offset: snapshot.context.selection.focus.offset + 1, }, }, }, }, ], ],})The execute action also has a shorthand function:
const autoCloseParens = defineBehavior({ on: 'insert.text', guard: ({snapshot, event}) => { return event.text === '(' }, actions: [ ({snapshot, event}) => [ execute(event), // ... ], ],})Emoji Picker
Section titled “Emoji Picker”An emoji picker that triggers when you insert : is available as a separate plugin package.

Test it out in the Playground.
Install the package:
npm install @portabletext/plugin-emoji-pickerUse the useEmojiPicker hook to handle the state and logic:
import { createMatchEmojis, useEmojiPicker,} from '@portabletext/plugin-emoji-picker'
const matchEmojis = createMatchEmojis({ emojis: { '😂': ['joy', 'laugh'], '😹': ['joy_cat'], },})
function EmojiPickerComponent() { const {keyword, matches, selectedIndex, onDismiss, onNavigateTo, onSelect} = useEmojiPicker({matchEmojis})
// Render your emoji picker UI using these values}Raise events
Section titled “Raise events”Sometimes you want to trigger an event from within an action. This sends the event back to the editor, where the editor treats it like any other event.
const raisedUppercaseA = defineBehavior({ on: 'insert.text', guard: ({snapshot, event}) => event.text === 'a', actions: [ ({snapshot, event}) => [ {type: 'raise', event: {type: 'insert.text', text: 'A'}}, ], ],})The raise action also has a shorthand function:
const raisedUppercaseA = defineBehavior({ on: 'insert.text', guard: ({snapshot, event}) => event.text === 'a', actions: [({snapshot, event}) => [raise({type: 'insert.text', text: 'A'})]],})