From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
    • Stream
    • Copilot
  • Discussion
    • Comments
    • Suggestion
    • Basic Blocks
      • Blockquote
      • Heading
      • Horizontal Rule
    • Callout
    • Code Block
    • Column
    • Date
    • Equation
    • Link
    • List Classic
    • Media
    • MentionElement
    • Table
    • Table of Contents
    • Footnote
    • Toggle
  • Marks
    • Bold
    • Italic
    • Underline
    • Code
    • Highlight
    • Keyboard Input
    • Strikethrough
    • Subscript
    • Superscript
      • Font
      • Line Height
      • Text Align
    • Indent
    • List
      • Exit Break
      • Single Block
      • Trailing Block
    • Autoformat
    • Block Menu
    • Block Placeholder
    • Combobox
      • Emoji
      • MentionElement
      • Slash Command
    • Cursor Overlay
    • Drag & Drop
    • Navigation Feedback
    • Tabbable
    • Toolbar
    • Yjs
    • Multi SelectEditor
    • CSV
    • DOCX
    • HTML
    • Markdown

Mention

PreviousNext

Inline markable void mentions backed by trigger combobox input.

ComboboxMention NodesInline Combobox

Mention turns trigger text such as @ into an inline combobox input and inserts a markable void mention node when the user selects an item. The package owns trigger detection, input node creation, mention insertion, selection movement, and Markdown mention serialization. The registry owns the demo item list and the inline combobox UI.

Loading…
MediaTable

On This Page

FeaturesFast PathAdd The KitRender MentionsAdd Static RenderingOwnershipManual SetupInstall PackageAdd PluginsSelect An ItemValue ShapeTrigger FlowMarkdownAPI Reference
Build your editor
Production-ready AI template and reusable components.
Get all-access

Features

  • Inline void mention nodes with required ref and optional label.
  • Inline void mentionInput nodes created by trigger text.
  • Trigger combobox support from @platejs/combobox.
  • Configurable trigger strings, regexes, and trigger queries.
  • Markable void rendering so bold, italic, and underline can style a mention.
  • Optional trailing space after selected mention items.
  • Markdown format through [display text](mention:id) plus bare @name deserialization.
Report an issue

Fast Path

Add The Kit

MentionKit installs MentionPlugin, MentionInputPlugin, the registry mention nodes, and a trigger rule that allows @ at the start of a line, after whitespace, or after quotes.

'use client';
 
import { MentionInputPlugin, MentionPlugin } from '@platejs/mention/react';
import { IS_APPLE } from 'platejs';
import {
  type PlateElementProps,
  PlateElement,
  useEditorFocused,
  useEditorReadOnly,
  useElementSelected,
} from 'platejs/react';
import * as React from 'react';
 
import { cn } from '@/lib/utils';
import { useMounted } from '@/hooks/use-mounted';
import { inlineSuggestionVariants } from '@/lib/inline-suggestion';
 
import {
  InlineCombobox,
  InlineComboboxContent,
  InlineComboboxEmpty,
  InlineComboboxGroup,
  InlineComboboxInput,
  InlineComboboxItem,
} from './inline-combobox';
 
export function MentionElement(
  props: PlateElementProps<typeof MentionPlugin> & {
    prefix?: string;
  }
) {
  const { element } = props;
  const selected = useElementSelected();
  const focused = useEditorFocused();
  const mounted = useMounted();
  const readOnly = useEditorReadOnly();
  const label = element.label ?? element.ref;
 
  return (
    <PlateElement
      {...props}
      className={cn(
        'inline-block rounded-md bg-muted px-1.5 py-0.5 align-baseline font-medium text-sm',
        inlineSuggestionVariants(),
        !readOnly && 'cursor-pointer',
        selected && focused && 'ring-2 ring-ring',
        element.children[0].bold === true && 'font-bold',
        element.children[0].italic === true && 'italic',
        element.children[0].underline === true && 'underline'
      )}
      attributes={{
        ...props.attributes,
        contentEditable: false,
        'data-plite-value': label,
        draggable: true,
      }}
    >
      {mounted && IS_APPLE ? (
        // Mac OS IME https://github.com/ianstormtaylor/slate/issues/3490
        <>
          {props.children}
          {props.prefix}
          {label}
        </>
      ) : (
        // Others like Android https://github.com/ianstormtaylor/slate/pull/5360
        <>
          {props.prefix}
          {label}
          {props.children}
        </>
      )}
    </PlateElement>
  );
}
 
export function MentionInputElement(
  props: PlateElementProps<typeof MentionInputPlugin>
) {
  const { editor, element } = props;
  const [search, setSearch] = React.useState('');
 
  return (
    <PlateElement {...props} as="span">
      <InlineCombobox
        value={search}
        element={element}
        setValue={setSearch}
        showTrigger={false}
        trigger="@"
      >
        <span className="inline-block rounded-md bg-muted px-1.5 py-0.5 align-baseline text-sm ring-ring focus-within:ring-2">
          <InlineComboboxInput />
        </span>
 
        <InlineComboboxContent className="my-1.5">
          <InlineComboboxEmpty>No results</InlineComboboxEmpty>
 
          <InlineComboboxGroup>
            {MENTIONABLES.map((item) => (
              <InlineComboboxItem
                key={item.ref}
                value={item.label}
                onClick={() => {
                  editor
                    .plugin(MentionPlugin)
                    .update.insert({ label: item.label, ref: item.ref });
                }}
              >
                {item.label}
              </InlineComboboxItem>
            ))}
          </InlineComboboxGroup>
        </InlineComboboxContent>
      </InlineCombobox>
 
      {props.children}
    </PlateElement>
  );
}
 
const MENTIONABLES = [
  { ref: '0', label: 'Aayla Secura' },
  { ref: '1', label: 'Adi Gallia' },
  {
    ref: '2',
    label: 'Admiral Dodd Rancit',
  },
  {
    ref: '3',
    label: 'Admiral Firmus Piett',
  },
  {
    ref: '4',
    label: 'Admiral Gial Ackbar',
  },
  { ref: '5', label: 'Admiral Ozzel' },
  { ref: '6', label: 'Admiral Raddus' },
  {
    ref: '7',
    label: 'Admiral Terrinald Screed',
  },
  { ref: '8', label: 'Admiral Trench' },
  {
    ref: '9',
    label: 'Admiral U.O. Statura',
  },
  { ref: '10', label: 'Agen Kolar' },
  { ref: '11', label: 'Agent Kallus' },
  {
    ref: '12',
    label: 'Aiolin and Morit Astarte',
  },
  { ref: '13', label: 'Aks Moe' },
  { ref: '14', label: 'Almec' },
  { ref: '15', label: 'Alton Kastle' },
  { ref: '16', label: 'Amee' },
  { ref: '17', label: 'AP-5' },
  { ref: '18', label: 'Armitage Hux' },
  { ref: '19', label: 'Artoo' },
  { ref: '20', label: 'Arvel Crynyd' },
  { ref: '21', label: 'Asajj Ventress' },
  { ref: '22', label: 'Aurra Sing' },
  { ref: '23', label: 'AZI-3' },
  { ref: '24', label: 'Bala-Tik' },
  { ref: '25', label: 'Barada' },
  { ref: '26', label: 'Bargwill Tomder' },
  { ref: '27', label: 'Baron Papanoida' },
  { ref: '28', label: 'Barriss Offee' },
  { ref: '29', label: 'Baze Malbus' },
  { ref: '30', label: 'Bazine Netal' },
  { ref: '31', label: 'BB-8' },
  { ref: '32', label: 'BB-9E' },
  { ref: '33', label: 'Ben Quadinaros' },
  { ref: '34', label: 'Berch Teller' },
  { ref: '35', label: 'Beru Lars' },
  { ref: '36', label: 'Bib Fortuna' },
  {
    ref: '37',
    label: 'Biggs Darklighter',
  },
  { ref: '38', label: 'Black Krrsantan' },
  { ref: '39', label: 'Bo-Katan Kryze' },
  { ref: '40', label: 'Boba Fett' },
  { ref: '41', label: 'Bobbajo' },
  { ref: '42', label: 'Bodhi Rook' },
  { ref: '43', label: 'Borvo the Hutt' },
  { ref: '44', label: 'Boss Nass' },
  { ref: '45', label: 'Bossk' },
  {
    ref: '46',
    label: 'Breha Antilles-Organa',
  },
  { ref: '47', label: 'Bren Derlin' },
  { ref: '48', label: 'Brendol Hux' },
  { ref: '49', label: 'BT-1' },
];
 
export const MentionKit = [
  MentionPlugin.configure({
    component: MentionElement,
    initialState: {
      triggerPreviousCharPattern: /^$|^[\s"']$/,
    },
  }),
  MentionInputPlugin.configure({ component: MentionInputElement }),
];
'use client';
 
import { MentionInputPlugin, MentionPlugin } from '@platejs/mention/react';
import { IS_APPLE } from 'platejs';
import {
  type PlateElementProps,
  PlateElement,
  useEditorFocused,
  useEditorReadOnly,
  useElementSelected,
} from 'platejs/react';
import * as React from 'react';
 
import { cn } from '@/lib/utils';
import { useMounted } from '@/hooks/use-mounted';
import { inlineSuggestionVariants } from '@/lib/inline-suggestion';
 
import {




























































































































































































import { createPlateEditor } from 'platejs/react';
 
import { MentionKit } from '@/components/editor/mention';
 
export const editor = createPlateEditor({
  plugins: MentionKit,
});
import { createPlateEditor } from 'platejs/react';
 
import { MentionKit } from '@/components/editor/mention';
 
export const editor = createPlateEditor({
  plugins: MentionKit,

Render Mentions

mention renders both the selected mention and the temporary combobox input. The demo data lives in that registry UI file, so replace it with your app users, pages, or records.

'use client';
 
import { MentionInputPlugin, MentionPlugin } from '@platejs/mention/react';
import { IS_APPLE } from 'platejs';
import {
  type PlateElementProps,
  PlateElement,
  useEditorFocused,
  useEditorReadOnly,
  useElementSelected,
} from 'platejs/react';
import * as React from 'react';
 
import { cn } from '@/lib/utils';
import { useMounted } from '@/hooks/use-mounted';
import { inlineSuggestionVariants } from '@/lib/inline-suggestion';
 
import {




























































































































































































Add Static Rendering

mention-static uses BaseMentionPlugin with the static mention node for read-only output.

import { BaseMentionPlugin } from '@platejs/mention';
import { type PliteElementProps, PliteElement } from 'platejs/static';
import * as React from 'react';
 
import { cn } from '@/lib/utils';
import { inlineSuggestionVariants } from '@/lib/inline-suggestion';
 
export function MentionElementStatic(
  props: PliteElementProps<typeof BaseMentionPlugin> & {
    prefix?: string;
  }
) {
  const { prefix } = props;
  const





























Ownership

LayerOwnerWhat It Does
@platejs/mentionPackageExports BaseMentionPlugin, BaseMentionInputPlugin, and the scoped mention update.
@platejs/mention/reactPackageExports MentionPlugin and MentionInputPlugin.
@platejs/comboboxPackageProvides trigger options and combobox input hooks.
mentionRegistryAdds mention plugins with editable mention and input components.
mention-staticRegistryAdds BaseMentionPlugin.configure({ component: MentionElementStatic }).
mentionRegistry UIRenders mention atoms, the inline combobox input, and demo items.
inline-comboboxRegistry UIRenders the Ariakit-backed popover, input, groups, items, and empty state.
@platejs/markdownPackageSerializes mentions as mention: links and deserializes mention links or bare mentions.

Manual Setup

Install Package

pnpm add @platejs/mention
pnpm add @platejs/mention

Add Plugins

MentionPlugin installs MentionInputPlugin as a required dependency. Add the complete input descriptor beside it when you need to replace the dependency's render component.

import { MentionInputPlugin, MentionPlugin } from '@platejs/mention/react';
import { createPlateEditor } from 'platejs/react';
 
import {
  MentionElement,
  MentionInputElement,
} from '@/components/editor/mention';
 
export const editor = createPlateEditor({
  plugins: [
    MentionPlugin.configure({
      component: MentionElement,
      initialState: {
        triggerPreviousCharPattern: /^$|^[\s"']$/,
      },
    }),
    MentionInputPlugin.configure({ component: MentionInputElement }),
  ],
});

Select An Item

Call the installed mention update from your combobox item renderer. It inserts the mention, moves the cursor after it, and inserts a trailing space only when insertSpaceAfterMention is enabled and the mention lands at the end of the block.

<InlineComboboxItem
  value={item.label}
  onClick={() =>
    editor.plugin(MentionPlugin).update.insert({
      label: item.label,
      ref: item.ref,
    })
  }
>
  {item.label}
</InlineComboboxItem>;
<InlineComboboxItem
  value={item.label}
  onClick={() =>
    editor.plugin(MentionPlugin).update.






Value Shape

PLUGINS.mention and PLUGINS.mentionInput identify the two capabilities. Their default persisted element types are mention and mentionInput. Reusable code reads configured types through each plugin portal's schema.type.

const value = [
  {
    children: [
      { text: 'Assigned to ' },
      {
        children: [{ text: '' }],
        label: 'Jane Smith',
        ref: 'user_123',
        type: 'mention',
      },
      { text: '.' },
    ],
    type: 'paragraph',
  },
];
const value = [
  {
    children: [
      { text: 'Assigned to ' },
      {
        children: [{ text: '' }],
        label: 'Jane Smith',
        ref: 'user_123',
        type: 'mention',
      },
      { text: '.' },
    ],
    type: 'paragraph',
  },
];
FieldTypeNotes
type'mention'Inline void mention node.
refstringRequired persisted association with the mentioned entity.
labelstringOptional visible text; rendering falls back to ref.
children[{ text: '' }]Empty child required for Plite inline void nodes.

The mention element declares schema.element.void: 'markable-inline', so marks on its empty child can style the rendered mention.

Trigger Flow

The runtime creates a combobox input from trigger metadata only when every gate passes.

GateSource
Inserted text matches triggerstring, string[], or RegExp.
Insert is not using options.atProgrammatic text insertion bypasses the trigger.
Editor has a selectionNo selection means no inline input target.
triggerQuery(editor) returns trueOptional app veto for custom contexts.
Previous character matches triggerPreviousCharPatternDefaults to /^\s?$/; registry kit uses /^$|^[\s"']$/.

The default createComboboxInput creates:

 
{
  children: [{ text: '' }],
  trigger: '@',
  type: 'mentionInput',
}
 
{
  children: [{ text: '' }],
  trigger: '@',
  type: 'mentionInput',
}

If editor.runtime.userId exists, the combobox input stores that userId so only the creator sees the transient input in collaborative editors.

Markdown

@platejs/markdown serializes ref in the link-style mention: URL and uses label ?? ref as visible text.

Hello [Jane Smith](mention:user_123).
Hello [Jane Smith](mention:user_123).

Deserialization supports link-style mentions and bare @alice text. Normal links such as [@docs](/docs/mention) stay links.

API Reference

APIPackageUse
BaseMentionPlugin@platejs/mentionHeadless inline markable void mention plugin with trigger-combobox behavior and a required BaseMentionInputPlugin dependency.
BaseMentionInputPlugin@platejs/mentionRequired inline void input node inserted while the combobox is active.
MentionPlugin@platejs/mention/reactReact mention plugin with a required MentionInputPlugin dependency.
MentionInputPlugin@platejs/mention/reactReact mention input dependency; add it directly to replace its component or configuration.
editor.plugin(MentionPlugin).update.insert({ ref, label? })BaseMentionPlugin updateInserts the mention node at the current selection.
inline-comboboxCopied registry UIHandles focus, cancellation, arrow/backspace/escape behavior, and undo/redo forwarding for the mention input.
InlineCombobox,
InlineComboboxContent,
InlineComboboxEmpty,
InlineComboboxGroup,
InlineComboboxInput,
InlineComboboxItem,
} from './inline-combobox';
export function MentionElement(
props: PlateElementProps<typeof MentionPlugin> & {
prefix?: string;
}
) {
const { element } = props;
const selected = useElementSelected();
const focused = useEditorFocused();
const mounted = useMounted();
const readOnly = useEditorReadOnly();
const label = element.label ?? element.ref;
return (
<PlateElement
{...props}
className={cn(
'inline-block rounded-md bg-muted px-1.5 py-0.5 align-baseline font-medium text-sm',
inlineSuggestionVariants(),
!readOnly && 'cursor-pointer',
selected && focused && 'ring-2 ring-ring',
element.children[0].bold === true && 'font-bold',
element.children[0].italic === true && 'italic',
element.children[0].underline === true && 'underline'
)}
attributes={{
...props.attributes,
contentEditable: false,
'data-plite-value': label,
draggable: true,
}}
>
{mounted && IS_APPLE ? (
// Mac OS IME https://github.com/ianstormtaylor/slate/issues/3490
<>
{props.children}
{props.prefix}
{label}
</>
) : (
// Others like Android https://github.com/ianstormtaylor/slate/pull/5360
<>
{props.prefix}
{label}
{props.children}
</>
)}
</PlateElement>
);
}
export function MentionInputElement(
props: PlateElementProps<typeof MentionInputPlugin>
) {
const { editor, element } = props;
const [search, setSearch] = React.useState('');
return (
<PlateElement {...props} as="span">
<InlineCombobox
value={search}
element={element}
setValue={setSearch}
showTrigger={false}
trigger="@"
>
<span className="inline-block rounded-md bg-muted px-1.5 py-0.5 align-baseline text-sm ring-ring focus-within:ring-2">
<InlineComboboxInput />
</span>
<InlineComboboxContent className="my-1.5">
<InlineComboboxEmpty>No results</InlineComboboxEmpty>
<InlineComboboxGroup>
{MENTIONABLES.map((item) => (
<InlineComboboxItem
key={item.ref}
value={item.label}
onClick={() => {
editor
.plugin(MentionPlugin)
.update.insert({ label: item.label, ref: item.ref });
}}
>
{item.label}
</InlineComboboxItem>
))}
</InlineComboboxGroup>
</InlineComboboxContent>
</InlineCombobox>
{props.children}
</PlateElement>
);
}
const MENTIONABLES = [
{ ref: '0', label: 'Aayla Secura' },
{ ref: '1', label: 'Adi Gallia' },
{
ref: '2',
label: 'Admiral Dodd Rancit',
},
{
ref: '3',
label: 'Admiral Firmus Piett',
},
{
ref: '4',
label: 'Admiral Gial Ackbar',
},
{ ref: '5', label: 'Admiral Ozzel' },
{ ref: '6', label: 'Admiral Raddus' },
{
ref: '7',
label: 'Admiral Terrinald Screed',
},
{ ref: '8', label: 'Admiral Trench' },
{
ref: '9',
label: 'Admiral U.O. Statura',
},
{ ref: '10', label: 'Agen Kolar' },
{ ref: '11', label: 'Agent Kallus' },
{
ref: '12',
label: 'Aiolin and Morit Astarte',
},
{ ref: '13', label: 'Aks Moe' },
{ ref: '14', label: 'Almec' },
{ ref: '15', label: 'Alton Kastle' },
{ ref: '16', label: 'Amee' },
{ ref: '17', label: 'AP-5' },
{ ref: '18', label: 'Armitage Hux' },
{ ref: '19', label: 'Artoo' },
{ ref: '20', label: 'Arvel Crynyd' },
{ ref: '21', label: 'Asajj Ventress' },
{ ref: '22', label: 'Aurra Sing' },
{ ref: '23', label: 'AZI-3' },
{ ref: '24', label: 'Bala-Tik' },
{ ref: '25', label: 'Barada' },
{ ref: '26', label: 'Bargwill Tomder' },
{ ref: '27', label: 'Baron Papanoida' },
{ ref: '28', label: 'Barriss Offee' },
{ ref: '29', label: 'Baze Malbus' },
{ ref: '30', label: 'Bazine Netal' },
{ ref: '31', label: 'BB-8' },
{ ref: '32', label: 'BB-9E' },
{ ref: '33', label: 'Ben Quadinaros' },
{ ref: '34', label: 'Berch Teller' },
{ ref: '35', label: 'Beru Lars' },
{ ref: '36', label: 'Bib Fortuna' },
{
ref: '37',
label: 'Biggs Darklighter',
},
{ ref: '38', label: 'Black Krrsantan' },
{ ref: '39', label: 'Bo-Katan Kryze' },
{ ref: '40', label: 'Boba Fett' },
{ ref: '41', label: 'Bobbajo' },
{ ref: '42', label: 'Bodhi Rook' },
{ ref: '43', label: 'Borvo the Hutt' },
{ ref: '44', label: 'Boss Nass' },
{ ref: '45', label: 'Bossk' },
{
ref: '46',
label: 'Breha Antilles-Organa',
},
{ ref: '47', label: 'Bren Derlin' },
{ ref: '48', label: 'Brendol Hux' },
{ ref: '49', label: 'BT-1' },
];
export const MentionKit = [
MentionPlugin.configure({
component: MentionElement,
initialState: {
triggerPreviousCharPattern: /^$|^[\s"']$/,
},
}),
MentionInputPlugin.configure({ component: MentionInputElement }),
];
});
InlineCombobox,
InlineComboboxContent,
InlineComboboxEmpty,
InlineComboboxGroup,
InlineComboboxInput,
InlineComboboxItem,
} from './inline-combobox';
export function MentionElement(
props: PlateElementProps<typeof MentionPlugin> & {
prefix?: string;
}
) {
const { element } = props;
const selected = useElementSelected();
const focused = useEditorFocused();
const mounted = useMounted();
const readOnly = useEditorReadOnly();
const label = element.label ?? element.ref;
return (
<PlateElement
{...props}
className={cn(
'inline-block rounded-md bg-muted px-1.5 py-0.5 align-baseline font-medium text-sm',
inlineSuggestionVariants(),
!readOnly && 'cursor-pointer',
selected && focused && 'ring-2 ring-ring',
element.children[0].bold === true && 'font-bold',
element.children[0].italic === true && 'italic',
element.children[0].underline === true && 'underline'
)}
attributes={{
...props.attributes,
contentEditable: false,
'data-plite-value': label,
draggable: true,
}}
>
{mounted && IS_APPLE ? (
// Mac OS IME https://github.com/ianstormtaylor/slate/issues/3490
<>
{props.children}
{props.prefix}
{label}
</>
) : (
// Others like Android https://github.com/ianstormtaylor/slate/pull/5360
<>
{props.prefix}
{label}
{props.children}
</>
)}
</PlateElement>
);
}
export function MentionInputElement(
props: PlateElementProps<typeof MentionInputPlugin>
) {
const { editor, element } = props;
const [search, setSearch] = React.useState('');
return (
<PlateElement {...props} as="span">
<InlineCombobox
value={search}
element={element}
setValue={setSearch}
showTrigger={false}
trigger="@"
>
<span className="inline-block rounded-md bg-muted px-1.5 py-0.5 align-baseline text-sm ring-ring focus-within:ring-2">
<InlineComboboxInput />
</span>
<InlineComboboxContent className="my-1.5">
<InlineComboboxEmpty>No results</InlineComboboxEmpty>
<InlineComboboxGroup>
{MENTIONABLES.map((item) => (
<InlineComboboxItem
key={item.ref}
value={item.label}
onClick={() => {
editor
.plugin(MentionPlugin)
.update.insert({ label: item.label, ref: item.ref });
}}
>
{item.label}
</InlineComboboxItem>
))}
</InlineComboboxGroup>
</InlineComboboxContent>
</InlineCombobox>
{props.children}
</PlateElement>
);
}
const MENTIONABLES = [
{ ref: '0', label: 'Aayla Secura' },
{ ref: '1', label: 'Adi Gallia' },
{
ref: '2',
label: 'Admiral Dodd Rancit',
},
{
ref: '3',
label: 'Admiral Firmus Piett',
},
{
ref: '4',
label: 'Admiral Gial Ackbar',
},
{ ref: '5', label: 'Admiral Ozzel' },
{ ref: '6', label: 'Admiral Raddus' },
{
ref: '7',
label: 'Admiral Terrinald Screed',
},
{ ref: '8', label: 'Admiral Trench' },
{
ref: '9',
label: 'Admiral U.O. Statura',
},
{ ref: '10', label: 'Agen Kolar' },
{ ref: '11', label: 'Agent Kallus' },
{
ref: '12',
label: 'Aiolin and Morit Astarte',
},
{ ref: '13', label: 'Aks Moe' },
{ ref: '14', label: 'Almec' },
{ ref: '15', label: 'Alton Kastle' },
{ ref: '16', label: 'Amee' },
{ ref: '17', label: 'AP-5' },
{ ref: '18', label: 'Armitage Hux' },
{ ref: '19', label: 'Artoo' },
{ ref: '20', label: 'Arvel Crynyd' },
{ ref: '21', label: 'Asajj Ventress' },
{ ref: '22', label: 'Aurra Sing' },
{ ref: '23', label: 'AZI-3' },
{ ref: '24', label: 'Bala-Tik' },
{ ref: '25', label: 'Barada' },
{ ref: '26', label: 'Bargwill Tomder' },
{ ref: '27', label: 'Baron Papanoida' },
{ ref: '28', label: 'Barriss Offee' },
{ ref: '29', label: 'Baze Malbus' },
{ ref: '30', label: 'Bazine Netal' },
{ ref: '31', label: 'BB-8' },
{ ref: '32', label: 'BB-9E' },
{ ref: '33', label: 'Ben Quadinaros' },
{ ref: '34', label: 'Berch Teller' },
{ ref: '35', label: 'Beru Lars' },
{ ref: '36', label: 'Bib Fortuna' },
{
ref: '37',
label: 'Biggs Darklighter',
},
{ ref: '38', label: 'Black Krrsantan' },
{ ref: '39', label: 'Bo-Katan Kryze' },
{ ref: '40', label: 'Boba Fett' },
{ ref: '41', label: 'Bobbajo' },
{ ref: '42', label: 'Bodhi Rook' },
{ ref: '43', label: 'Borvo the Hutt' },
{ ref: '44', label: 'Boss Nass' },
{ ref: '45', label: 'Bossk' },
{
ref: '46',
label: 'Breha Antilles-Organa',
},
{ ref: '47', label: 'Bren Derlin' },
{ ref: '48', label: 'Brendol Hux' },
{ ref: '49', label: 'BT-1' },
];
export const MentionKit = [
MentionPlugin.configure({
component: MentionElement,
initialState: {
triggerPreviousCharPattern: /^$|^[\s"']$/,
},
}),
MentionInputPlugin.configure({ component: MentionInputElement }),
];
'use client';
 
import { MentionInputPlugin, MentionPlugin } from '@platejs/mention/react';
import { IS_APPLE } from 'platejs';
import {
  type PlateElementProps,
  PlateElement,
  useEditorFocused,
  useEditorReadOnly,
  useElementSelected,
} from 'platejs/react';
import * as React from 'react';
 
import { cn } from '@/lib/utils';
import { useMounted } from '@/hooks/use-mounted';
import { inlineSuggestionVariants } from '@/lib/inline-suggestion';
 
import {
  InlineCombobox,
  InlineComboboxContent,
  InlineComboboxEmpty,
  InlineComboboxGroup,
  InlineComboboxInput,
  InlineComboboxItem,
} from './inline-combobox';
 
export function MentionElement(
  props: PlateElementProps<typeof MentionPlugin> & {
    prefix?: string;
  }
) {
  const { element } = props;
  const selected = useElementSelected();
  const focused = useEditorFocused();
  const mounted = useMounted();
  const readOnly = useEditorReadOnly();
  const label = element.label ?? element.ref;
 
  return (
    <PlateElement
      {...props}
      className={cn(
        'inline-block rounded-md bg-muted px-1.5 py-0.5 align-baseline font-medium text-sm',
        inlineSuggestionVariants(),
        !readOnly && 'cursor-pointer',
        selected && focused && 'ring-2 ring-ring',
        element.children[0].bold === true && 'font-bold',
        element.children[0].italic === true && 'italic',
        element.children[0].underline === true && 'underline'
      )}
      attributes={{
        ...props.attributes,
        contentEditable: false,
        'data-plite-value': label,
        draggable: true,
      }}
    >
      {mounted && IS_APPLE ? (
        // Mac OS IME https://github.com/ianstormtaylor/slate/issues/3490
        <>
          {props.children}
          {props.prefix}
          {label}
        </>
      ) : (
        // Others like Android https://github.com/ianstormtaylor/slate/pull/5360
        <>
          {props.prefix}
          {label}
          {props.children}
        </>
      )}
    </PlateElement>
  );
}
 
export function MentionInputElement(
  props: PlateElementProps<typeof MentionInputPlugin>
) {
  const { editor, element } = props;
  const [search, setSearch] = React.useState('');
 
  return (
    <PlateElement {...props} as="span">
      <InlineCombobox
        value={search}
        element={element}
        setValue={setSearch}
        showTrigger={false}
        trigger="@"
      >
        <span className="inline-block rounded-md bg-muted px-1.5 py-0.5 align-baseline text-sm ring-ring focus-within:ring-2">
          <InlineComboboxInput />
        </span>
 
        <InlineComboboxContent className="my-1.5">
          <InlineComboboxEmpty>No results</InlineComboboxEmpty>
 
          <InlineComboboxGroup>
            {MENTIONABLES.map((item) => (
              <InlineComboboxItem
                key={item.ref}
                value={item.label}
                onClick={() => {
                  editor
                    .plugin(MentionPlugin)
                    .update.insert({ label: item.label, ref: item.ref });
                }}
              >
                {item.label}
              </InlineComboboxItem>
            ))}
          </InlineComboboxGroup>
        </InlineComboboxContent>
      </InlineCombobox>
 
      {props.children}
    </PlateElement>
  );
}
 
const MENTIONABLES = [
  { ref: '0', label: 'Aayla Secura' },
  { ref: '1', label: 'Adi Gallia' },
  {
    ref: '2',
    label: 'Admiral Dodd Rancit',
  },
  {
    ref: '3',
    label: 'Admiral Firmus Piett',
  },
  {
    ref: '4',
    label: 'Admiral Gial Ackbar',
  },
  { ref: '5', label: 'Admiral Ozzel' },
  { ref: '6', label: 'Admiral Raddus' },
  {
    ref: '7',
    label: 'Admiral Terrinald Screed',
  },
  { ref: '8', label: 'Admiral Trench' },
  {
    ref: '9',
    label: 'Admiral U.O. Statura',
  },
  { ref: '10', label: 'Agen Kolar' },
  { ref: '11', label: 'Agent Kallus' },
  {
    ref: '12',
    label: 'Aiolin and Morit Astarte',
  },
  { ref: '13', label: 'Aks Moe' },
  { ref: '14', label: 'Almec' },
  { ref: '15', label: 'Alton Kastle' },
  { ref: '16', label: 'Amee' },
  { ref: '17', label: 'AP-5' },
  { ref: '18', label: 'Armitage Hux' },
  { ref: '19', label: 'Artoo' },
  { ref: '20', label: 'Arvel Crynyd' },
  { ref: '21', label: 'Asajj Ventress' },
  { ref: '22', label: 'Aurra Sing' },
  { ref: '23', label: 'AZI-3' },
  { ref: '24', label: 'Bala-Tik' },
  { ref: '25', label: 'Barada' },
  { ref: '26', label: 'Bargwill Tomder' },
  { ref: '27', label: 'Baron Papanoida' },
  { ref: '28', label: 'Barriss Offee' },
  { ref: '29', label: 'Baze Malbus' },
  { ref: '30', label: 'Bazine Netal' },
  { ref: '31', label: 'BB-8' },
  { ref: '32', label: 'BB-9E' },
  { ref: '33', label: 'Ben Quadinaros' },
  { ref: '34', label: 'Berch Teller' },
  { ref: '35', label: 'Beru Lars' },
  { ref: '36', label: 'Bib Fortuna' },
  {
    ref: '37',
    label: 'Biggs Darklighter',
  },
  { ref: '38', label: 'Black Krrsantan' },
  { ref: '39', label: 'Bo-Katan Kryze' },
  { ref: '40', label: 'Boba Fett' },
  { ref: '41', label: 'Bobbajo' },
  { ref: '42', label: 'Bodhi Rook' },
  { ref: '43', label: 'Borvo the Hutt' },
  { ref: '44', label: 'Boss Nass' },
  { ref: '45', label: 'Bossk' },
  {
    ref: '46',
    label: 'Breha Antilles-Organa',
  },
  { ref: '47', label: 'Bren Derlin' },
  { ref: '48', label: 'Brendol Hux' },
  { ref: '49', label: 'BT-1' },
];
 
export const MentionKit = [
  MentionPlugin.configure({
    component: MentionElement,
    initialState: {
      triggerPreviousCharPattern: /^$|^[\s"']$/,
    },
  }),
  MentionInputPlugin.configure({ component: MentionInputElement }),
];
{
element
}
=
props;
const label = element.label ?? element.ref;
return (
<PliteElement
{...props}
as="span"
className={cn(
'inline-block rounded-md bg-muted px-1.5 py-0.5 align-baseline font-medium text-sm',
inlineSuggestionVariants(),
element.children[0].bold === true && 'font-bold',
element.children[0].italic === true && 'italic',
element.children[0].underline === true && 'underline'
)}
attributes={{
...props.attributes,
'data-plite-value': label,
}}
>
{props.children}
{prefix}
{label}
</PliteElement>
);
}
export const BaseMentionKit = [
BaseMentionPlugin.configure({
component: MentionElementStatic,
}),
];
import { BaseMentionPlugin } from '@platejs/mention';
import { type PliteElementProps, PliteElement } from 'platejs/static';
import * as React from 'react';
 
import { cn } from '@/lib/utils';
import { inlineSuggestionVariants } from '@/lib/inline-suggestion';
 
export function MentionElementStatic(
  props: PliteElementProps<typeof BaseMentionPlugin> & {
    prefix?: string;
  }
) {
  const { prefix } = props;
  const { element } = props;
  const label = element.label ?? element.ref;
 
  return (
    <PliteElement
      {...props}
      as="span"
      className={cn(
        'inline-block rounded-md bg-muted px-1.5 py-0.5 align-baseline font-medium text-sm',
        inlineSuggestionVariants(),
        element.children[0].bold === true && 'font-bold',
        element.children[0].italic === true && 'italic',
        element.children[0].underline === true && 'underline'
      )}
      attributes={{
        ...props.attributes,
        'data-plite-value': label,
      }}
    >
      {props.children}
      {prefix}
      {label}
    </PliteElement>
  );
}
 
export const BaseMentionKit = [
  BaseMentionPlugin.configure({
    component: MentionElementStatic,
  }),
];
import { MentionInputPlugin, MentionPlugin } from '@platejs/mention/react';
import { createPlateEditor } from 'platejs/react';
 
import {
  MentionElement,
  MentionInputElement,
} from '@/components/editor/mention';
 
export const editor = createPlateEditor({
  plugins: [
    MentionPlugin.configure({
      component: MentionElement,
      initialState: {
        triggerPreviousCharPattern: /^$|^[\s"']$/,
      },
    }),
    MentionInputPlugin.configure({ component: MentionInputElement }),
  ],
});
insert
({
label: item.label,
ref: item.ref,
})
}
>
{item.label}
</InlineComboboxItem>;