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>

This is the standalone build. On a page that loads YurbaUI use the .ui build instead, see Builds.

The picker is created entirely from JavaScript via the static YurbaEP.create() factory - no HTML markup needed. With no arguments it loads the emoji set from the relative paths /static/emoji/noto/emoji.json and /static/emoji/noto/png/, which only resolve on a page served by yurba.one itself; other sites pass emojiJson and notoBase. 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.
Builds

Two builds with the same API. They differ only in how the picker is shown on a small screen.

BuildFilesMade ofOn a small screen
yurba-ep dist/yurba-ep.min.js
dist/yurba-ep.min.css
yurba-ep.js
yurba-ep.css + yurba-ep.standalone.css
No dependencies. Up to 1280px wide the standalone CSS pins the popup to the bottom edge at full width.
yurba-ep.ui dist/yurba-ep.ui.min.js
dist/yurba-ep.ui.min.css
yurba-ep.js + yurba-ep.ui.js
yurba-ep.css + yurba-ep.ui.css
Up to 768px wide the picker opens in a YurbaUI.Modal sheet; closing the sheet closes the picker. Wider, it is the same floating popup as the standalone build.

Standalone:

<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@24,400,0,0">
<link rel="stylesheet" href="/dist/yurba-ep.min.css">
<script src="/dist/yurba-ep.min.js"></script>

With YurbaUI:

<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@24,400,0,0">
<link rel="stylesheet" href="/dist/yurba-ui.min.css">
<link rel="stylesheet" href="/dist/yurba-ep.ui.min.css">
<script src="/dist/yurba-ui.min.js"></script>
<script src="/dist/yurba-ep.ui.min.js"></script>

The .ui build checks for the YurbaUI global each time the picker opens. Without it the picker stays a floating popup on a phone too, and this build has no bottom pinning of its own.

Both builds draw the close, search and category icons with Material Symbols Rounded, so the page loads that font (any of them can be replaced with icons and groupHtml). Colors follow the Yurba theme variables when the page defines them, see CSS variables.


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.
closeLabel optional string 'Close' Accessible name of the close button.
allLabel optional string 'All' Accessible name of the "all" category tab.
searchLabel optional string 'Search...' Placeholder and accessible name of the search field.
emojiJson optional string https://cdn.yurba.one/static/emoji/noto/emoji.json URL to the emoji JSON file. The default is on the Yurba CDN, which lets any site load it.
notoBase optional string https://cdn.yurba.one/static/emoji/noto/png/ Base URL for Noto Color Emoji PNGs, on the Yurba CDN like emojiJson.
groupHtml optional object - Category tab icon overrides, merged over the built-in defaults. Keys are emoji-metadata group names or 'all'. See Category icons.
icons optional object - Override the other built-in icons. Keys: close, search, all, category. Each value is an HTML string. See Icons.
customEmojis optional array - Custom emoji categories appended after the standard ones. See Custom emojis.
lottie optional object window.lottie The lottie-web module, for a page that imports it rather than loading it as a global. Only animated custom emoji use it, 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, on a page served by yurba.one, where the default relative paths resolve:

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

Full config:

const picker = YurbaEP.create({
    title:       'Choose emoji',
    emojiJson:   'https://cdn.yurba.one/static/emoji/noto/emoji.json',
    notoBase:    'https://cdn.yurba.one/static/emoji/noto/png/',
    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(only?)
ParamTypeDescription
only optional string[] Native emoji to offer instead of the full set, with no categories, as a chat allowing only some reactions does. The pick is exactly the given emoji. Leave it out, or pass an empty array, for the full set.

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() (by default the relative /static/emoji/noto/emoji.json).

picker.open(['👍', '❤️', '😂'])
close
picker.close()

Hides the picker. Also closes any open skin tone variant popup. Triggered automatically on outside click, page scroll, or the ✕ button. Escape closes it too; when the skin tone popup is open, the first Escape closes only that popup.

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, ✕, Escape, 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.
nativestring | nullThe emoji as text, null for a custom one. A skin tone has no shortcode of its own: it comes with the code and shortcode of its base emoji and the tone only here, so prefer native where text is fine.
// 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, Escape, 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
                },
                {
                    id: 'cat',
                    src: 'https://cdn.yurba.one/static/emoji/yurba/animated/cat.json',
                    animated: true,
                    still: 'https://example.com/emoji/cat.png'
                },
                ...
            ]
        }
    ]
})
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). For an animated emoji, the URL of its Lottie JSON.
keywords optional string[] Extra search keywords. The id is always searchable.
animated optional boolean Marks the emoji as animated (default false): src is then a Lottie JSON file, which the picker shows still, on its first frame, so the grid stays calm. Exposed as animated in the yurba-ep.select event detail so you can play it where it is used.
still optional string An animated emoji's first frame as a plain picture. lottie-web is optional: with it on the page (the lottie global or the lottie option) the picker draws the frame from the JSON; without it the picker shows still, and an animated emoji with no still is left out.
Icons

The built-in icons outside the category tabs are set with the icons config in create(). Each value is an HTML string inserted as given; keys left out keep the default.

KeyWhereDefault
closeClose button in the headerclose Material Symbol
searchSearch field. A custom icon is wrapped in .y-ep__search-icon, so it keeps the color and focus stylingsearch Material Symbol
allThe "all" tabmore_horiz Material Symbol
categoryTab of a JSON group that has no icon of its own in groupHtmlemoji_emotions Material Symbol
const picker = YurbaEP.create({
    icons: {
        close:  '<svg viewBox="0 0 24 24" width="20" height="20">…</svg>',
        search: '<i class="fa fa-search"></i>',
    },
})

Use icons for these four and groupHtml for the tab of a named group. groupHtml.all still sets the "all" tab, icons.all wins when both are given. A custom category brings its own tab icon in its html.

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. A group not listed here gets the category icon from icons.
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 .y-ep and .y-ep__variants (the skin tone popup lives in <body>) in a stylesheet loaded after the picker's; a parent does not work, since the picker sets them on itself. Each default follows a Yurba theme variable (--yurba-main-color, --yurba-brand-color and others) when the page defines it and falls back to the built-in value below otherwise.

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;
}