Skip to content

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:

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),
],
],
})

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),
// ...
],
],
})

An emoji picker that triggers when you insert : is available as a separate plugin package.

Emoji picker in Portable Text Editor

Test it out in the Playground.

Install the package:

Terminal window
npm install @portabletext/plugin-emoji-picker

Use 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
}

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'})]],
})