Skip to content

Emoji

The Emoji extension adds inline emoji nodes with :shortcode: input rules, optional emoticon shortcuts (:), <3), a headless suggestion plugin for autocomplete dropdowns, and built-in frequency tracking. The extension ships as a separate package (@domternal/extension-emoji).

Type : followed by a name to open the suggestion dropdown, or use shortcodes like :smile: to insert emoji directly.

With the default theme enabled. Click the smiley toolbar button to trigger the suggestion dropdown, or type : followed by a name.

Click to try it out
Terminal window
pnpm add @domternal/extension-emoji
import { Editor, Document, Paragraph, Text } from '@domternal/core';
import { Emoji, emojis, createEmojiSuggestionRenderer } from '@domternal/extension-emoji';
import '@domternal/theme';
const editor = new Editor({
element: document.getElementById('editor')!,
extensions: [
Document, Paragraph, Text,
Emoji.configure({
emojis,
suggestion: { render: createEmojiSuggestionRenderer() },
}),
],
content: '<p>Type :smile: to insert an emoji!</p>',
});
// Handle the toolbar emoji button - open inline suggestion
editor.on('insertEmoji', () => {
editor.commands.suggestEmoji();
});

Type : followed by a shortcode (e.g. :rocket:) to see the inline suggestion dropdown. The toolbar emoji button emits an insertEmoji event - wire it to suggestEmoji() or build a custom picker panel.

PropertyValue
ProseMirror nameemoji
TypeNode
Groupinline
ContentNone (atom node)
AtomYes
InlineYes
SelectableYes
DraggableNo
HTML tag<span> (parses span[data-type="emoji"] and span[data-emoji-name])

Emoji is an inline atom node. It cannot contain text or other nodes. Click an emoji to select it (creates a NodeSelection), then press Backspace to delete or type to replace.

OptionTypeDefaultDescription
emojisEmojiItem[]Built-in ~218 emojiEmoji dataset used for shortcode resolution, search, and rendering
enableEmoticonsbooleanfalseEnable emoticon shortcuts like :) and <3 (33 emoticons)
plainTextbooleanfalseInsert emoji as plain Unicode text instead of atom nodes
HTMLAttributesRecord<string, unknown>{}HTML attributes added to the emoji <span> element
suggestionSuggestionOptions | nullnullSuggestion plugin config for autocomplete. Pass null to disable

Two datasets are included:

DatasetSizeDescription
emojis218 emojiPopular emoji across all categories (~5 KB gzipped)
allEmojis516 emojiExtended Unicode set (~11 KB gzipped)
import { Emoji, emojis, allEmojis } from '@domternal/extension-emoji';
// Default: 218 popular emoji
const DefaultEmoji = Emoji.configure({ emojis });
// Extended: 516 emoji
const ExtendedEmoji = Emoji.configure({ emojis: allEmojis });

You can also provide a fully custom dataset:

import type { EmojiItem } from '@domternal/extension-emoji';
const customEmojis: EmojiItem[] = [
{
emoji: '\ud83d\ude80',
name: 'rocket',
shortcodes: ['rocket'],
tags: ['launch', 'space'],
group: 'Travel & Places',
},
// ...
];
const CustomEmoji = Emoji.configure({ emojis: customEmojis });
interface EmojiItem {
emoji: string; // Native Unicode character (e.g., "\ud83d\ude04")
name: string; // Unique name used as node attribute (e.g., "smile")
shortcodes: string[]; // Shortcodes without colons (e.g., ["smile", "grinning"])
tags: string[]; // Search tags (e.g., ["happy", "face"])
group: string; // Category group (e.g., "Smileys & Emotion")
fallbackImage?: string; // Optional fallback image URL for unsupported emoji
}

When enableEmoticons: true, common text emoticons are converted to emoji on space:

EmoticonEmojiName
:) :-) (:\ud83d\ude42slightly smiling face
:D :-D\ud83d\ude03grinning face with big eyes
;) ;-)\ud83d\ude09winking face
:( :-(\ud83d\ude41slightly frowning face
:P :-P :p\ud83d\ude1bface with tongue
:O :-O :o\ud83d\ude2eface with open mouth
:/ :-/ :\\ud83d\ude15confused face
B) 8)\ud83d\ude0esmiling face with sunglasses
:* :-*\ud83d\ude17kissing face
>:( >:-(\ud83d\ude20angry face
XD xD\ud83d\ude02face with tears of joy
:$\ud83d\ude33flushed face
O:)\ud83d\ude07smiling face with halo
>:)\ud83d\ude08smiling face with horns
:| :-|\ud83d\ude10neutral face
<3\u2764\ufe0fred heart
</3\ud83d\udc94broken heart

Emoticons must be preceded by a space (or be at the start of a line) and followed by a space to trigger. They are not converted inside code blocks or inline code.

const PlainEmoji = Emoji.configure({ plainText: true });

When plainText: true, emoji are inserted as regular Unicode text characters instead of atom nodes. Shortcodes and emoticons still work, but the result is a text node (\ud83d\ude04) rather than a structured emoji node. This is useful when you need simple emoji input without node-level selection or custom rendering.

AttributeTypeDefaultDescription
namestring | nullnullThe emoji name from the dataset (e.g., "grinning_face_with_smiling_eyes")

The name attribute is stored as data-name in the HTML output. When parsing, both data-name and data-emoji-name (legacy format) are supported.

Insert an emoji by name at the current cursor position.

editor.commands.insertEmoji('grinning_face_with_smiling_eyes');
editor.commands.insertEmoji('red_heart');
editor.commands.insertEmoji('thumbs_up');
// With chaining
editor.chain().focus().insertEmoji('rocket').run();

Returns false if:

  • The emoji name is not found in the dataset
  • The cursor is inside a code block

Inserting an emoji automatically tracks it in the frequency map (used by getFrequentlyUsed()).

Programmatically open the suggestion dropdown by inserting the trigger character.

editor.commands.suggestEmoji();

Returns false if the suggestion plugin is not configured (suggestion: null). When configured, inserts the trigger character (default :) at the cursor, which activates the suggestion plugin.

Emoji does not register any keyboard shortcuts. The suggestion plugin intercepts these keys while active:

KeyAction (when suggestion is open)
ArrowDownSelect next item
ArrowUpSelect previous item
EnterInsert selected emoji
EscapeClose suggestion dropdown
InputResult
:smile:\ud83d\ude04 (grinning face with smiling eyes)
:heart:\u2764\ufe0f (red heart)
:thumbsup:\ud83d\udc4d (thumbs up)

The pattern :shortcode: matches any shortcode from the emoji dataset. Shortcodes are defined in each EmojiItem’s shortcodes array. The trailing : triggers the conversion.

Shortcode input rules are skipped inside code blocks and inline code marks.

InputResult
:) \ud83d\ude42 (slightly smiling face)
<3 \u2764\ufe0f (red heart)
:D \ud83d\ude03 (grinning face with big eyes)

Emoticons require enableEmoticons: true and are triggered by a trailing space. They must be preceded by a space or appear at the start of a line.

Emoji registers a button in the toolbar with the name emoji in group insert at priority 50.

ItemCommandIconEvent
Insert EmojiinsertEmojismileyinsertEmoji

Clicking the toolbar button emits an insertEmoji event. In a full setup, the Angular <domternal-emoji-picker> component (or a custom handler) listens for this event and opens a picker panel. The emitEvent property means the button does not execute the command directly.

Handle the insertEmoji event to show a custom picker or fall back to suggestEmoji:

import { Editor, Document, Paragraph, Text } from '@domternal/core';
import { Emoji, emojis, createEmojiSuggestionRenderer } from '@domternal/extension-emoji';
const editor = new Editor({
element: document.getElementById('editor')!,
extensions: [
Document, Paragraph, Text,
Emoji.configure({
emojis,
suggestion: { render: createEmojiSuggestionRenderer() },
}),
],
});
// Option 1: Open inline suggestion when toolbar button is clicked
editor.on('insertEmoji', () => {
editor.commands.suggestEmoji();
});
// Option 2: Build a custom picker panel
editor.on('insertEmoji', ({ anchorElement }) => {
// anchorElement is the toolbar button - use it for positioning
showCustomEmojiPicker(anchorElement);
});

The suggestion plugin provides inline autocomplete when the user types the trigger character (default :). It is a headless ProseMirror plugin with a pluggable renderer.

import { Emoji, emojis, createEmojiSuggestionRenderer } from '@domternal/extension-emoji';
const EmojiWithSuggestion = Emoji.configure({
emojis,
suggestion: {
char: ':', // Trigger character (default)
minQueryLength: 0, // Minimum chars before showing results (default)
allowSpaces: false, // Allow spaces in query (default)
render: createEmojiSuggestionRenderer(), // Vanilla DOM renderer
items: ({ query }) => { // Optional custom filter
// Return filtered/sorted EmojiItem[]
},
},
});
OptionTypeDefaultDescription
charstring':'Trigger character that activates the suggestion
minQueryLengthnumber0Minimum query length before showing suggestions
allowSpacesbooleanfalseAllow spaces in the query string
items(props: { query: string }) => EmojiItem[]undefinedCustom filter/sort function. Falls back to storage.searchEmoji()
render() => SuggestionRendererundefinedFactory that creates a renderer instance

The suggestion activates when:

  1. The trigger character is typed at the start of a line or after a space
  2. The query contains only alphanumeric characters, underscores, dashes, and plus signs
  3. The cursor is not inside a code block or inline code mark

The suggestion deactivates when:

  • The user presses Escape
  • The query no longer matches (e.g., cursor moves away)
  • An emoji is inserted

The built-in createEmojiSuggestionRenderer() renders a vanilla DOM dropdown:

  • Shows up to 10 matching items
  • Positioned below the cursor using Floating UI
  • Keyboard navigation: ArrowUp/ArrowDown to select, Enter to insert
  • Mouse: click to insert, hover to highlight
  • Appends inside .dm-editor for scroll-synchronized positioning
  • Shows “No emoji found” when no matches

Implement the SuggestionRenderer interface for a custom UI:

import type { SuggestionRenderer, SuggestionProps } from '@domternal/extension-emoji';
function createCustomRenderer(): () => SuggestionRenderer {
return () => {
return {
onStart(props: SuggestionProps) {
// Create and show your dropdown UI
// props.items - matching emoji
// props.command(item) - call to insert an emoji
// props.clientRect() - cursor position for positioning
// props.element - ProseMirror editor DOM element
},
onUpdate(props: SuggestionProps) {
// Update your UI with new items/query
},
onExit() {
// Destroy your dropdown UI
},
onKeyDown(event: KeyboardEvent): boolean {
// Handle ArrowUp/Down/Enter - return true to prevent default
return false;
},
};
};
}

The Emoji extension provides storage methods for searching and tracking emoji usage.

// Find an emoji by exact name
const item = editor.storage.emoji.findEmoji('red_heart');
// => { emoji: '\u2764\ufe0f', name: 'red_heart', shortcodes: ['heart', 'red_heart'], ... }
// Search emoji by name, shortcode, or tag
const results = editor.storage.emoji.searchEmoji('happy');
// => [{ emoji: '\ud83d\ude00', name: 'grinning_face', ... }, ...]
// Get frequently used emoji names (sorted by usage count)
const frequent = editor.storage.emoji.getFrequentlyUsed();
// => ['red_heart', 'thumbs_up', 'smile', ...]
// Record a usage (done automatically by insertEmoji and input rules)
editor.storage.emoji.addFrequentlyUsed('rocket');
MethodReturnsDescription
findEmoji(name)EmojiItem | undefinedFind emoji by exact name
searchEmoji(query)EmojiItem[]Search by name, shortcodes, or tags (case-insensitive)
getFrequentlyUsed()string[]Emoji names sorted by usage count (most used first)
addFrequentlyUsed(name)voidIncrement usage count for an emoji name

The <domternal-emoji-picker> component provides a full-featured picker panel for Angular applications.

import { DomternalEmojiPickerComponent } from '@domternal/angular';
import { emojis } from '@domternal/extension-emoji';
@Component({
imports: [DomternalEmojiPickerComponent],
template: `
<domternal-emoji-picker
[editor]="editor()!"
[emojis]="emojiData"
/>
`,
})
export class EditorComponent {
emojiData = emojis;
}
InputTypeRequiredDescription
editorEditorYesThe Domternal editor instance
emojisEmojiPickerItem[]YesEmoji dataset to display in the picker
  • Search - real-time filtering by name, shortcode, and tags
  • Category tabs - click to scroll to a category, auto-highlights on scroll
  • Frequently used - shows up to 16 most-inserted emoji at the top
  • Keyboard support - Escape closes the picker
  • Click outside - closes the picker
  • Auto-positioning - uses Floating UI to position below the toolbar button
  • Auto-focus - search input is focused when the picker opens
  • Focus return - editor is re-focused when the picker closes

The picker listens for the insertEmoji event emitted by the toolbar button. It toggles open/closed on each click.

interface EmojiPickerItem {
emoji: string; // Native Unicode character
name: string; // Unique name
group: string; // Category for tab grouping
}

This is a subset of EmojiItem. You can pass the emojis or allEmojis arrays directly.

Emoji styles are provided by @domternal/theme in _emoji-picker.scss:

ClassDescription
.emojiInline emoji span (cursor: default, no italic)
.emoji.ProseMirror-selectednodeSelected state (accent-colored outline)
ClassDescription
.dm-emoji-pickerMain picker panel (absolute, 20rem wide, max 22rem tall)
.dm-emoji-picker-searchSearch input container
.dm-emoji-picker-tabsCategory tab bar
.dm-emoji-picker-tabIndividual category tab (2rem square)
.dm-emoji-picker-tab--activeActive tab state
.dm-emoji-picker-gridScrollable emoji grid (8 columns)
.dm-emoji-picker-category-labelCategory heading in grid
.dm-emoji-swatchIndividual emoji button (2rem square, hover: scale 1.15)
.dm-emoji-picker-empty”No emoji found” message
ClassDescription
.dm-emoji-suggestionSuggestion dropdown container (absolute, 12-18rem wide)
.dm-emoji-suggestion-itemIndividual suggestion row
.dm-emoji-suggestion-item--selectedActive/highlighted row
.dm-emoji-suggestion-emojiEmoji character in suggestion row
.dm-emoji-suggestion-nameEmoji name text in suggestion row
.dm-emoji-suggestion-empty”No emoji found” in suggestion

All components support light and dark themes automatically via CSS custom properties (--dm-surface, --dm-border-color, --dm-text, --dm-hover, --dm-active, --dm-accent).

import { Emoji, emojis, allEmojis, emoticons } from '@domternal/extension-emoji';
import { createEmojiSuggestionRenderer } from '@domternal/extension-emoji';
import { createSuggestionPlugin, emojiSuggestionPluginKey } from '@domternal/extension-emoji';
import type {
EmojiOptions, EmojiStorage, EmojiItem,
SuggestionOptions, SuggestionProps, SuggestionRenderer,
} from '@domternal/extension-emoji';
ExportTypeDescription
EmojiNode extensionThe emoji node extension
emojisEmojiItem[]Default dataset (~218 popular emoji)
allEmojisEmojiItem[]Extended dataset (~516 emoji)
emoticonsRecord<string, string>Emoticon-to-name mapping (33 entries)
createEmojiSuggestionRenderer() => () => SuggestionRendererFactory for the vanilla DOM suggestion dropdown
createSuggestionPlugin(options) => PluginLow-level ProseMirror suggestion plugin factory
emojiSuggestionPluginKeyPluginKeyPlugin key for accessing suggestion state
EmojiOptionsTypeScript typeOptions for Emoji.configure()
EmojiStorageTypeScript typeStorage methods interface
EmojiItemTypeScript typeEmoji data item interface
SuggestionOptionsTypeScript typeSuggestion plugin options
SuggestionPropsTypeScript typeProps passed to suggestion renderer callbacks
SuggestionRendererTypeScript typeRenderer interface for custom suggestion UI

An emoji node:

{
"type": "emoji",
"attrs": {
"name": "grinning_face_with_smiling_eyes"
}
}

A paragraph with text and emoji:

{
"type": "paragraph",
"content": [
{ "type": "text", "text": "Hello " },
{ "type": "emoji", "attrs": { "name": "waving_hand" } },
{ "type": "text", "text": " welcome!" }
]
}

A document with multiple emoji:

{
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{ "type": "text", "text": "Great job " },
{ "type": "emoji", "attrs": { "name": "thumbs_up" } },
{ "type": "emoji", "attrs": { "name": "party_popper" } }
]
}
]
}

@domternal/extension-emoji - Emoji.ts