YurbaEP

YurbaEmojiPicker

Renders Noto Color Emoji (Google) from PNG files. Loads emoji data from a developer-supplied JSON with lazy pagination, dynamic category tabs, search, skin tone variant popups, and support for custom emojis.

Usage
<link rel="stylesheet" href="/dist/yurba-ep.min.css">
<script src="/dist/yurba-ep.min.js"></script>

The picker is created entirely from JavaScript via the static YurbaEP.create() factory — no HTML markup needed. With no arguments it loads the default Yurba CDN emoji set. It appends itself to document.body and returns the element.

const picker = YurbaEP.create()

picker.bind(
    document.querySelector('#emoji-btn'),
    document.querySelector('#message-input')
)
On selection a yurba-ep.select event always fires on the bound field. The picker also inserts the :emoji-code: shortcode at the caret of an <input> / <textarea>; for a contenteditable target it inserts the emoji <img> when insertImage is on, or leaves insertion to your event handler otherwise.

Live demo

<textarea> target:

<input> target:

contenteditable target:


API

All public methods available on the <yurba-ep> element instance.

create
const picker = YurbaEP.create(config)

Factory method — the only way to create a picker. Creates the <yurba-ep> element, appends it to document.body, and returns it.

Config keyTypeDefaultDescription
title optional string 'Pick an emoji' Text shown in the picker header.
emojiJson optional string https://cdn.yurba.one/static/noto-emoji/emoji.json URL to the emoji JSON file.
notoBase optional string https://cdn.yurba.one/static/noto-emoji/png/72/ Base URL for Noto Color Emoji PNGs.
groupHtml optional object Category tab icon overrides, merged over the built-in defaults. Keys are emoji-metadata group names or 'all'. See Category icons.
customEmojis optional array Custom emoji categories appended after the standard ones. See Custom emojis.
insertImage optional boolean false Controls insertion into a contenteditable target. When true the picker inserts the emoji <img> (with alt=":code:" and data-emoji="code"). When false the picker inserts nothing into the editable — handle the yurba-ep.select event (which always fires) and render the emoji yourself. Has no effect on <input> / <textarea>, which always receive the :code: shortcode.

Minimal — uses the default Yurba CDN emoji set:

const picker = YurbaEP.create()
picker.bind(emojiBtn, messageInput)

Full config:

const picker = YurbaEP.create({
    title:       'Choose emoji',
    emojiJson:   'https://cdn.yurba.one/static/noto-emoji/emoji.json',
    notoBase:    'https://cdn.yurba.one/static/noto-emoji/png/72/',
    insertImage: true,
    groupHtml: {
        'all':   '<span class="material-symbols-rounded">more_horiz</span>',
        'Flags': '<span class="material-symbols-rounded">flag</span>',
    },
    customEmojis: [
        {
            name: 'Yurba',
            id: 'yurba',
            html: '<span class="material-symbols-rounded">diamond</span>',
            emojis: [
                {
                    id: 'crystal',
                    keywords: ['crystal', 'diamond', 'yurba'],
                    src: 'https://cdn.yurba.one/static/emoji/yurba/png/crystal.png',
                    animated: false
                }
            ]
        }
    ],
})
The returned element is already in the DOM. You can call .bind() on it immediately.
bind
picker.bind(button, input)
ParamTypeDescription
button required HTMLElement Trigger element. Clicking it opens the picker positioned relative to this element; clicking it again while open closes the picker.
input required HTMLInputElement | HTMLTextAreaElement | contenteditable Target field. On selection a yurba-ep.select event always fires on this element. The picker then inserts at the caret: for <input> / <textarea> the :code: shortcode (into .value); for a contenteditable element the emoji <img> when insertImage is on, otherwise nothing — leaving you to render it via the event. A native input event is dispatched after any built-in insertion.
picker.bind(
    document.querySelector('#emoji-btn'),
    document.querySelector('#message-input')
)
open
picker.open()

Shows the picker. On the first call, fetches the emoji JSON and renders the initial tab. Subsequent calls skip the fetch. Uses the emojiJson passed to create() (defaults to the Yurba CDN set).

close
picker.close()

Hides the picker. Also closes any open skin tone variant popup. Triggered automatically on outside click, page scroll, or the ✕ button.

selectTab
picker.selectTab(tabId)
ParamTypeDescription
tabId required string Tab ID to activate. Tab IDs are derived from JSON group names — spaces and special characters replaced with underscores, lowercased. Custom category IDs are also accepted.

Built-in tab IDs, derived from the emoji-metadata group names: all, smileys_and_emotions, people, animals_and_nature, food_and_drink, travel_and_places, activities_and_events, objects, symbols, flags. Plus any custom category id.


Events

All events are dispatched as bubbling CustomEvents, namespaced under yurba-ep.*. Lifecycle events (open, close, load) fire on the <yurba-ep> element; yurba-ep.select fires on the bound field so you can intercept insertion.

Overview
EventFires onDescription
yurba-ep.select bound field An emoji was chosen. Always fires, before any built-in insertion.
yurba-ep.open <yurba-ep> The picker became visible.
yurba-ep.close <yurba-ep> The picker was hidden (outside click, scroll, ✕, trigger re-click, or close()).
yurba-ep.load <yurba-ep> The emoji JSON finished loading and the first tab rendered.
yurba-ep.select

Always fired on the bound field when an emoji is chosen, before any built-in insertion. The picker's own insertion is independent: <input> / <textarea> always get the :code: shortcode; a contenteditable target gets the emoji <img> only when insertImage is on — otherwise the picker leaves it untouched, so you render the emoji yourself in this handler.

event.detailTypeDescription
codestringEmoji code, e.g. grinning.
shortcodestringThe code wrapped in colons, e.g. :grinning:.
srcstringImage URL of the emoji (Noto PNG or custom src).
animatedbooleantrue for a custom emoji flagged animated; otherwise false.
// insertImage = false → the picker won't touch the
// contenteditable, so you render the emoji however you like.
const editor = document.querySelector('#editor')
picker.bind(emojiBtn, editor)

editor.addEventListener('yurba-ep.select', (e) => {
    const { code, shortcode, src } = e.detail

    const img = document.createElement('img')
    img.src = src
    img.alt = shortcode
    img.className = 'emoji'

    editor.append(img)
})
yurba-ep.open

Fired on the <yurba-ep> element when the picker becomes visible.

picker.addEventListener('yurba-ep.open', () => {
    console.log('picker opened')
})
yurba-ep.close

Fired on the <yurba-ep> element when the picker is hidden — by outside click, page scroll, the ✕ button, re-clicking the trigger, or a close() call.

picker.addEventListener('yurba-ep.close', () => {
    console.log('picker closed')
})
yurba-ep.load

Fired once on the <yurba-ep> element after the emoji JSON is fetched and the first tab is rendered.

event.detailTypeDescription
countnumberTotal number of loaded emojis (standard + custom).
picker.addEventListener('yurba-ep.load', (e) => {
    console.log(`${e.detail.count} emojis ready`)
})

Customization

Custom emojis

Pass custom categories via the customEmojis config in create(). They are appended after the standard categories. Each category must have a name, an id, an html string for the tab icon, and an emojis array.

const picker = YurbaEP.create({
    customEmojis: [
        {
            name: 'Yurba',
            id: 'yurba',
            html: '<span class="material-symbols-rounded">diamond</span>',
            emojis: [
                {
                    id: 'crystal',
                    keywords: ['crystal', 'diamond', 'yurba'],
                    src: 'https://cdn.yurba.one/static/emoji/yurba/png/crystal.png',
                    animated: false
                },
                ...
            ]
        }
    ]
})
FieldTypeDescription
id required string Emoji code. Inserted into the bound field as :id: on selection, and used as a search keyword.
src required string Image URL, rendered as an <img>. Any format (PNG, SVG, GIF, WebP).
keywords optional string[] Extra search keywords. The id is always searchable.
animated optional boolean Marks the emoji as animated (default false). Exposed as animated in the yurba-ep.select event detail so you can render it differently — the picker itself does not treat it specially.
Category icons

Category tab icons are keyed by the emoji-metadata group name. Each value is the raw HTML rendered inside the .y-ep__category tab button. Override them via the groupHtml config in create() — it is merged over the built-in defaults, so you only list the ones you want to change.

const picker = YurbaEP.create({
    groupHtml: {
        'all':   '<span class="material-symbols-rounded">more_horiz</span>',
        'Flags': '<img src="https://cdn.yurba.one/static/emoji/yurba/png/crystal.png" width="20" height="20">',
    },
})

Available keys (match emoji-metadata group names exactly): all, Smileys and emotions, People, Animals and nature, Food and drink, Travel and places, Activities and events, Objects, Symbols, Flags.

Default icons use Material Symbols Rounded. Any HTML is accepted — <img>, <svg>, custom icon components, etc.
Emoji JSON format

The picker natively supports the official Google emoji-metadata format — an array of groups with decimal Unicode codepoints. Download emoji_17_0_ordering.json from that repo and host it yourself.

[
    {
        "group": "Smileys and emotions",
        "emoji": [
            {
                "base": [128075],
                "alternates": [[128075], [128075, 127995], [128075, 127996],
                               [128075, 127997], [128075, 127998], [128075, 127999]],
                "emoticons": [],
                "shortcodes": [":wave:"],
                "animated": false,
                "directional": false
            }
        ]
    }
]
FieldDescription
group Category name. Each group becomes its own tab. Tab ID is derived automatically: "Activities and events"activities_and_events. Unknown groups get a generic icon.
emoji[].base Decimal Unicode codepoints. Converted to hex for Noto filename: [128512]emoji_u1f600.png. U+FE0F (65039) is stripped automatically. Codepoints below U+1000 are zero-padded to 4 digits: [35]emoji_u0023.png.
emoji[].alternates Skin tone (and gender) variant codepoint arrays. alternates[0] repeats base and is skipped. The remaining entries are shown in a popup when the emoji is clicked.
emoji[].shortcodes Shortcode strings like ":wave:". The first one becomes the code appended to the input on selection (:wave:). Variants append a numeric suffix: :wave_1::wave_5:.
emoji[].emoticons Text emoticons like ":D" — used as additional search keywords.
CSS variables

The picker defines its own CSS variables on .y-ep with sensible defaults. Override them on the yurba-ep element or any parent to match your design — no dependency on external theme variables.

VariableDefaultDescription
--y-ep-bg #ffffff Picker background and gradient fade color.
--y-ep-secondary #f2f2f7 Secondary background — search bar, dividers, emoji hover, scrollbar.
--y-ep-text #000000 Text and icon color.
--y-ep-accent #007aff Accent color — active category tab, loader spinner.
/* Dark theme example */
yurba-ep {
    --y-ep-bg:        #1c1c1e;
    --y-ep-secondary: #2c2c2e;
    --y-ep-text:      #ffffff;
    --y-ep-accent:    #0a84ff;
}