Overview
The Mention plugin provides inline mentions for users, channels, pages, or custom entities. Features autocomplete dropdown, multiple trigger characters, and flexible search integration.
Installation
npm install @yoopta/mention
Basic Usage
import { useMemo } from 'react' ;
import YooptaEditor , { createYooptaEditor } from '@yoopta/editor' ;
import Mention from '@yoopta/mention' ;
import Paragraph from '@yoopta/paragraph' ;
const mentionPlugin = Mention . extend ({
options: {
onSearch : async ( query , trigger ) => {
// Fetch matching users/entities
const response = await fetch ( `/api/search?q= ${ query } &type= ${ trigger . type } ` );
return response . json ();
},
},
});
const plugins = [ Paragraph , mentionPlugin ];
export default function Editor () {
const editor = useMemo (() => createYooptaEditor ({ plugins , marks: [] }), []);
return < YooptaEditor editor = { editor } onChange = { () => {} } /> ;
}
Features
Multiple Triggers : Support @, #, [[, or custom trigger characters
Autocomplete : Dropdown with search results
Type Support : Users, channels, pages, custom types
Keyboard Navigation : Arrow keys, Enter, Escape
Debounced Search : Configurable search debouncing
Custom Rendering : Full control over mention and dropdown UI
Configuration
Single Trigger
import Mention from '@yoopta/mention' ;
const mentionPlugin = Mention . extend ({
options: {
char: '@' ,
onSearch : async ( query ) => {
return [
{ id: '1' , name: 'John Doe' , avatar: '/avatars/john.jpg' },
{ id: '2' , name: 'Jane Smith' , avatar: '/avatars/jane.jpg' },
];
},
},
});
Multiple Triggers
const mentionPlugin = Mention . extend ({
options: {
triggers: [
{ char: '@' , type: 'user' , allowSpaces: false },
{ char: '#' , type: 'channel' , allowSpaces: false },
{ char: '[[' , type: 'page' , allowSpaces: true },
],
onSearch : async ( query , trigger ) => {
// Different search based on trigger type
if ( trigger . type === 'user' ) {
return fetchUsers ( query );
} else if ( trigger . type === 'channel' ) {
return fetchChannels ( query );
} else if ( trigger . type === 'page' ) {
return fetchPages ( query );
}
},
},
});
Options
Multiple trigger configurations Show MentionTrigger properties
Trigger character(s) (e.g., ”@”, ”#”, ”[[”)
Type identifier (e.g., “user”, “channel”, “page”)
Allow spaces in search query
Pattern that must precede the trigger (whitespace or start)
Single trigger character (shorthand for triggers: [{ char }])
onSearch
(query: string, trigger: MentionTrigger) => Promise<MentionItem[]>
required
Search function called when user types after trigger Returns : Array of mention items with id, name, optional avatar, type, and meta
Debounce delay for search in milliseconds
Minimum query length before triggering search
onSelect
(item: MentionItem, trigger: MentionTrigger) => void
Called when a mention is selected
onOpen
(trigger: MentionTrigger) => void
Called when dropdown opens
Called when dropdown closes
Close dropdown when item is selected
Close dropdown on click outside
Close dropdown on Escape key
Element Props
Mention type (user, channel, page, etc.)
nodeType
'inlineVoid'
default: "inlineVoid"
Element type (always inlineVoid for mentions)
Commands
Mention commands are available via MentionCommands.
insertMention
Insert a mention at the current selection.
import Mention , { MentionCommands } from '@yoopta/mention' ;
MentionCommands . insertMention ( editor , {
slate: slateEditor ,
item: {
id: '123' ,
name: 'John Doe' ,
type: 'user' ,
avatar: '/avatars/john.jpg' ,
},
});
deleteMention
Remove a mention.
import { MentionCommands } from '@yoopta/mention' ;
MentionCommands . deleteMention ( editor , {
slate: slateEditor ,
});
buildMentionElements
Build mention element structure (used internally).
import { MentionCommands } from '@yoopta/mention' ;
const mentionElement = MentionCommands . buildMentionElements ( editor , {
item: { id: '123' , name: 'John Doe' },
});
Hooks
useMentionDropdown
Hook for building custom mention dropdown UI.
import { useMentionDropdown } from '@yoopta/mention' ;
function MentionDropdown () {
const {
isOpen ,
query ,
trigger ,
items ,
loading ,
error ,
selectedIndex ,
setSelectedIndex ,
selectItem ,
close ,
refs ,
floatingStyles ,
} = useMentionDropdown ( editor );
if ( ! isOpen ) return null ;
return (
< div ref = {refs. setFloating } style = { floatingStyles } >
{ loading && < div > Loading ...</ div >}
{ items . map (( item , index ) => (
< div
key = {item. id }
onClick = {() => selectItem ( item )}
className = { selectedIndex === index ? 'selected' : '' }
>
{ item . avatar && < img src = {item. avatar } /> }
{ item . name }
</ div >
))}
</ div >
);
}
useMentionState
Access mention state.
import { useMentionState } from '@yoopta/mention' ;
const { isOpen , query , trigger , targetRect , triggerRange } = useMentionState ( editor );
useMentionTriggerActive
Check if a specific trigger is active.
import { useMentionTriggerActive } from '@yoopta/mention' ;
const isUserMentionActive = useMentionTriggerActive ( editor , '@' );
Editor Extension
The mention plugin extends the editor with mention-specific methods:
import { MentionYooEditor } from '@yoopta/mention' ;
const editor = createYooptaEditor ({ plugins }) as MentionYooEditor ;
// Access mention state
editor . mentions . state . isOpen ;
editor . mentions . state . query ;
// Control dropdown
editor . mentions . open ({ trigger , targetRect , triggerRange });
editor . mentions . close ( 'escape' );
editor . mentions . setQuery ( 'john' );
// Select current item (set by useMentionDropdown)
editor . mentions . selectCurrentItem ?.();
Types
MentionItem
type MentionItem < TMeta = Record < string , unknown >> = {
id : string ;
type ?: MentionType ;
name : string ;
avatar ?: string ;
meta ?: TMeta ;
};
MentionType
type MentionType = 'user' | 'channel' | 'page' | 'custom' | string ;
Event Types
The plugin emits the following events:
mention:open - Dropdown opened
mention:close - Dropdown closed
mention:query-change - Search query changed
mention:select - Item selected
Examples
User Mentions with API
const mentionPlugin = Mention . extend ({
options: {
char: '@' ,
onSearch : async ( query ) => {
const response = await fetch ( `/api/users?search= ${ query } ` );
const users = await response . json ();
return users . map ( user => ({
id: user . id ,
name: user . name ,
avatar: user . avatar ,
type: 'user' ,
meta: { email: user . email },
}));
},
onSelect : ( item ) => {
console . log ( 'Mentioned user:' , item );
},
},
});
Multiple Entity Types
const mentionPlugin = Mention . extend ({
options: {
triggers: [
{ char: '@' , type: 'user' },
{ char: '#' , type: 'channel' },
{ char: '/' , type: 'command' },
],
onSearch : async ( query , trigger ) => {
const type = trigger . type || 'user' ;
const response = await fetch ( `/api/ ${ type } s?q= ${ query } ` );
return response . json ();
},
onSelect : ( item , trigger ) => {
console . log ( `Selected ${ trigger . type } :` , item );
},
},
});
Wiki-Style Links
const mentionPlugin = Mention . extend ({
options: {
char: '[[' ,
triggers: [{
char: '[[' ,
type: 'page' ,
allowSpaces: true ,
}],
onSearch : async ( query ) => {
const response = await fetch ( `/api/pages?search= ${ query } ` );
const pages = await response . json ();
return pages . map ( page => ({
id: page . id ,
name: page . title ,
type: 'page' ,
meta: { path: page . path },
}));
},
},
});
Parsers
HTML
Deserialize : <span> with data-mention-id and other data attributes
Serialize : Mention → <span> with data attributes for id, name, type, etc.
Markdown
Serialize : Mention → [@name](mention:id) format
Email
Serialize : <span> with inline styles and mention data