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).
Live Playground
Section titled “Live Playground”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.
Vanilla JS preview - Angular components produce the same output
Vanilla JS preview - React components produce the same output
Plain emoji without the theme. The buttons above the editor call commands like insertEmoji() and suggestEmoji() directly. Emoticons (:), <3) are enabled. Type : to see inline suggestions.
Installation
Section titled “Installation”pnpm add @domternal/extension-emojiimport { 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 suggestioneditor.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.
import { Component, signal } from '@angular/core';import { DomternalEditorComponent, DomternalToolbarComponent, DomternalEmojiPickerComponent,} from '@domternal/angular';import { Editor, Document, Paragraph, Text } from '@domternal/core';import { Emoji, emojis, createEmojiSuggestionRenderer } from '@domternal/extension-emoji';
@Component({ selector: 'app-editor', imports: [DomternalEditorComponent, DomternalToolbarComponent, DomternalEmojiPickerComponent], templateUrl: './editor.html',})export class EditorComponent { editor = signal<Editor | null>(null); emojiData = emojis; extensions = [ Document, Paragraph, Text, Emoji.configure({ emojis, suggestion: { render: createEmojiSuggestionRenderer() }, }), ]; content = '<p>Type :smile: to insert an emoji!</p>';}@if (editor(); as ed) { <domternal-toolbar [editor]="ed" />}<domternal-editor [extensions]="extensions" [content]="content" (editorCreated)="editor.set($event)"/>@if (editor(); as ed) { <domternal-emoji-picker [editor]="ed" [emojis]="emojiData" />}The <domternal-emoji-picker> automatically listens for the toolbar emoji button click and opens a full picker with search, categories, and frequently used.
import { Domternal } from '@domternal/react';import { Document, Paragraph, Text } from '@domternal/core';import { Emoji, emojis, createEmojiSuggestionRenderer } from '@domternal/extension-emoji';
export default function Editor() { return ( <Domternal extensions={[ Document, Paragraph, Text, Emoji.configure({ emojis, suggestion: { render: createEmojiSuggestionRenderer() }, }), ]} content="<p>Type :smile: to insert an emoji!</p>" > <Domternal.Toolbar /> <Domternal.Content /> <Domternal.EmojiPicker emojis={emojis} /> </Domternal> );}The <Domternal.EmojiPicker> automatically listens for the toolbar emoji button click and opens a full picker with search, categories, and frequently used.
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() }, }), ],});
// Insert an emoji by nameeditor.commands.insertEmoji('rocket');editor.commands.insertEmoji('red_heart');
// Open the inline suggestion dropdown programmaticallyeditor.commands.suggestEmoji();Schema
Section titled “Schema”| Property | Value |
|---|---|
| ProseMirror name | emoji |
| Type | Node |
| Group | inline |
| Content | None (atom node) |
| Atom | Yes |
| Inline | Yes |
| Selectable | Yes |
| Draggable | No |
| 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.
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
emojis | EmojiItem[] | Built-in ~218 emoji | Emoji dataset used for shortcode resolution, search, and rendering |
enableEmoticons | boolean | false | Enable emoticon shortcuts like :) and <3 (33 emoticons) |
plainText | boolean | false | Insert emoji as plain Unicode text instead of atom nodes |
HTMLAttributes | Record<string, unknown> | {} | HTML attributes added to the emoji <span> element |
suggestion | SuggestionOptions | null | null | Suggestion plugin config for autocomplete. Pass null to disable |
Emoji datasets
Section titled “Emoji datasets”Two datasets are included:
| Dataset | Size | Description |
|---|---|---|
emojis | 218 emoji | Popular emoji across all categories (~5 KB gzipped) |
allEmojis | 516 emoji | Extended Unicode set (~11 KB gzipped) |
import { Emoji, emojis, allEmojis } from '@domternal/extension-emoji';
// Default: 218 popular emojiconst DefaultEmoji = Emoji.configure({ emojis });
// Extended: 516 emojiconst 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 });EmojiItem interface
Section titled “EmojiItem interface”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}Emoticons
Section titled “Emoticons”When enableEmoticons: true, common text emoticons are converted to emoji on space:
| Emoticon | Emoji | Name |
|---|---|---|
:) :-) (: | \ud83d\ude42 | slightly smiling face |
:D :-D | \ud83d\ude03 | grinning face with big eyes |
;) ;-) | \ud83d\ude09 | winking face |
:( :-( | \ud83d\ude41 | slightly frowning face |
:P :-P :p | \ud83d\ude1b | face with tongue |
:O :-O :o | \ud83d\ude2e | face with open mouth |
:/ :-/ :\ | \ud83d\ude15 | confused face |
B) 8) | \ud83d\ude0e | smiling face with sunglasses |
:* :-* | \ud83d\ude17 | kissing face |
>:( >:-( | \ud83d\ude20 | angry face |
XD xD | \ud83d\ude02 | face with tears of joy |
:$ | \ud83d\ude33 | flushed face |
O:) | \ud83d\ude07 | smiling face with halo |
>:) | \ud83d\ude08 | smiling face with horns |
:| :-| | \ud83d\ude10 | neutral face |
<3 | \u2764\ufe0f | red heart |
</3 | \ud83d\udc94 | broken 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.
Plain text mode
Section titled “Plain text mode”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.
Attributes
Section titled “Attributes”| Attribute | Type | Default | Description |
|---|---|---|---|
name | string | null | null | The 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.
Commands
Section titled “Commands”insertEmoji
Section titled “insertEmoji”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 chainingeditor.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()).
suggestEmoji
Section titled “suggestEmoji”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.
Keyboard shortcuts
Section titled “Keyboard shortcuts”Emoji does not register any keyboard shortcuts. The suggestion plugin intercepts these keys while active:
| Key | Action (when suggestion is open) |
|---|---|
ArrowDown | Select next item |
ArrowUp | Select previous item |
Enter | Insert selected emoji |
Escape | Close suggestion dropdown |
Input rules
Section titled “Input rules”Shortcodes
Section titled “Shortcodes”| Input | Result |
|---|---|
: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.
Emoticons (when enabled)
Section titled “Emoticons (when enabled)”| Input | Result |
|---|---|
:) | \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.
Toolbar items
Section titled “Toolbar items”Main toolbar
Section titled “Main toolbar”Emoji registers a button in the toolbar with the name emoji in group insert at priority 50.
| Item | Command | Icon | Event |
|---|---|---|---|
| Insert Emoji | insertEmoji | smiley | insertEmoji |
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 clickededitor.on('insertEmoji', () => { editor.commands.suggestEmoji();});
// Option 2: Build a custom picker paneleditor.on('insertEmoji', ({ anchorElement }) => { // anchorElement is the toolbar button - use it for positioning showCustomEmojiPicker(anchorElement);});Use <domternal-emoji-picker> which automatically listens for the insertEmoji event:
<domternal-emoji-picker [editor]="editor()!" [emojis]="emojiData"/>Use <Domternal.EmojiPicker> which automatically listens for the insertEmoji event:
<Domternal.EmojiPicker emojis={emojis} />Without a toolbar, trigger the suggestion dropdown programmatically:
// Open the inline suggestion dropdowneditor.commands.suggestEmoji();
// Or insert an emoji directly by nameeditor.commands.insertEmoji('rocket');Suggestion plugin
Section titled “Suggestion plugin”The suggestion plugin provides inline autocomplete when the user types the trigger character (default :). It is a headless ProseMirror plugin with a pluggable renderer.
Configuration
Section titled “Configuration”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[] }, },});SuggestionOptions
Section titled “SuggestionOptions”| Option | Type | Default | Description |
|---|---|---|---|
char | string | ':' | Trigger character that activates the suggestion |
minQueryLength | number | 0 | Minimum query length before showing suggestions |
allowSpaces | boolean | false | Allow spaces in the query string |
items | (props: { query: string }) => EmojiItem[] | undefined | Custom filter/sort function. Falls back to storage.searchEmoji() |
render | () => SuggestionRenderer | undefined | Factory that creates a renderer instance |
Query matching
Section titled “Query matching”The suggestion activates when:
- The trigger character is typed at the start of a line or after a space
- The query contains only alphanumeric characters, underscores, dashes, and plus signs
- 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
Default renderer
Section titled “Default renderer”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/ArrowDownto select,Enterto insert - Mouse: click to insert, hover to highlight
- Appends inside
.dm-editorfor scroll-synchronized positioning - Shows “No emoji found” when no matches
Custom renderer
Section titled “Custom renderer”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; }, }; };}Storage
Section titled “Storage”The Emoji extension provides storage methods for searching and tracking emoji usage.
// Find an emoji by exact nameconst item = editor.storage.emoji.findEmoji('red_heart');// => { emoji: '\u2764\ufe0f', name: 'red_heart', shortcodes: ['heart', 'red_heart'], ... }
// Search emoji by name, shortcode, or tagconst 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');| Method | Returns | Description |
|---|---|---|
findEmoji(name) | EmojiItem | undefined | Find 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) | void | Increment usage count for an emoji name |
Angular emoji picker
Section titled “Angular emoji picker”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;}Inputs
Section titled “Inputs”| Input | Type | Required | Description |
|---|---|---|---|
editor | Editor | Yes | The Domternal editor instance |
emojis | EmojiPickerItem[] | Yes | Emoji dataset to display in the picker |
Features
Section titled “Features”- 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 -
Escapecloses 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.
EmojiPickerItem interface
Section titled “EmojiPickerItem interface”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.
Styling
Section titled “Styling”Emoji styles are provided by @domternal/theme in _emoji-picker.scss:
Inline emoji node
Section titled “Inline emoji node”| Class | Description |
|---|---|
.emoji | Inline emoji span (cursor: default, no italic) |
.emoji.ProseMirror-selectednode | Selected state (accent-colored outline) |
Emoji picker (Angular component)
Section titled “Emoji picker (Angular component)”| Class | Description |
|---|---|
.dm-emoji-picker | Main picker panel (absolute, 20rem wide, max 22rem tall) |
.dm-emoji-picker-search | Search input container |
.dm-emoji-picker-tabs | Category tab bar |
.dm-emoji-picker-tab | Individual category tab (2rem square) |
.dm-emoji-picker-tab--active | Active tab state |
.dm-emoji-picker-grid | Scrollable emoji grid (8 columns) |
.dm-emoji-picker-category-label | Category heading in grid |
.dm-emoji-swatch | Individual emoji button (2rem square, hover: scale 1.15) |
.dm-emoji-picker-empty | ”No emoji found” message |
Suggestion dropdown
Section titled “Suggestion dropdown”| Class | Description |
|---|---|
.dm-emoji-suggestion | Suggestion dropdown container (absolute, 12-18rem wide) |
.dm-emoji-suggestion-item | Individual suggestion row |
.dm-emoji-suggestion-item--selected | Active/highlighted row |
.dm-emoji-suggestion-emoji | Emoji character in suggestion row |
.dm-emoji-suggestion-name | Emoji 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).
Exports
Section titled “Exports”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';| Export | Type | Description |
|---|---|---|
Emoji | Node extension | The emoji node extension |
emojis | EmojiItem[] | Default dataset (~218 popular emoji) |
allEmojis | EmojiItem[] | Extended dataset (~516 emoji) |
emoticons | Record<string, string> | Emoticon-to-name mapping (33 entries) |
createEmojiSuggestionRenderer | () => () => SuggestionRenderer | Factory for the vanilla DOM suggestion dropdown |
createSuggestionPlugin | (options) => Plugin | Low-level ProseMirror suggestion plugin factory |
emojiSuggestionPluginKey | PluginKey | Plugin key for accessing suggestion state |
EmojiOptions | TypeScript type | Options for Emoji.configure() |
EmojiStorage | TypeScript type | Storage methods interface |
EmojiItem | TypeScript type | Emoji data item interface |
SuggestionOptions | TypeScript type | Suggestion plugin options |
SuggestionProps | TypeScript type | Props passed to suggestion renderer callbacks |
SuggestionRenderer | TypeScript type | Renderer interface for custom suggestion UI |
JSON representation
Section titled “JSON representation”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" } } ] } ]}Source
Section titled “Source”@domternal/extension-emoji - Emoji.ts