webdevpuneet.com

Emoji Picker — Free HTML CSS JS Snippet

Emoji Picker · Forms · Plain HTML, CSS & JS · Live preview

CategoryForms

Copy & paste

Source Code

<div class="page">
  <div class="chat-demo">
    <div class="chat-output" id="chat-output">
      <div class="chat-msg">Hey! How's it going? 👋</div>
      <div class="chat-msg outgoing">Pretty good! Just testing this emoji picker 😄</div>
    </div>

    <div class="input-row">
      <div class="emoji-wrap" id="emoji-wrap">
        <button class="emoji-trigger" id="emoji-trigger" onclick="togglePicker()" aria-label="Insert emoji" aria-haspopup="true" aria-expanded="false">😊</button>

        <div class="picker" id="picker" role="dialog" aria-label="Emoji picker">
          <div class="picker-search">
            <input class="search-inp" id="search-inp" type="text" placeholder="Search emoji…" oninput="filterEmoji(this.value)">
          </div>
          <div class="cat-tabs" id="cat-tabs">
            <button class="cat-tab active" data-cat="recent" onclick="showCat(this,'recent')">🕐</button>
            <button class="cat-tab" data-cat="smileys" onclick="showCat(this,'smileys')">😀</button>
            <button class="cat-tab" data-cat="people" onclick="showCat(this,'people')">👋</button>
            <button class="cat-tab" data-cat="nature" onclick="showCat(this,'nature')">🌿</button>
            <button class="cat-tab" data-cat="food" onclick="showCat(this,'food')">🍕</button>
            <button class="cat-tab" data-cat="symbols" onclick="showCat(this,'symbols')">💡</button>
          </div>
          <div class="emoji-grid" id="emoji-grid"></div>
          <div class="picker-footer" id="picker-footer">Click an emoji to add it</div>
        </div>
      </div>

      <input class="chat-input" id="chat-input" type="text" placeholder="Type a message…">
      <button class="send-btn" onclick="sendMsg()">Send</button>
    </div>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Insert at cursor: selectionStart read + string slice insert + cursor restore
Recent tab: unshift to recent array, deduplicate, cap at 12, re-render on select
6 category tabs: Recent/Smileys/People/Nature/Food/Symbols with CSS .active state
Scale+opacity popup animation: 0.96 scale → 1 on .open class
Hover scale on emoji buttons: transform:scale(1.2) on :hover
Click-outside close: document click + e.target.closest("#emoji-wrap") check
Search input: triggers filterEmoji() on every keystroke
Chat demo: sendMsg() appends outgoing messages, clears input, closes picker

About this UI Snippet

Emoji Picker — Category Tabs, Recent Emojis, Insert at Cursor & Chat Demo

Screenshot of the Emoji Picker snippet rendered live

Type "I'm so happy" into any chat box and it reads flat — type "I'm so happy 🎉" and it suddenly has a tone. That gap is why every messaging product, from the Chat Bubble UI you're replying in to the comment box under a blog post, ships its own emoji picker rather than relying on the operating system's. This snippet builds one from scratch: six categorised tabs (Recent, Smileys, People, Nature, Food, Symbols), a search field, a hover-scale emoji grid, a popover that inserts *exactly where the cursor is* rather than at the end of the line, a self-maintaining "recently used" tab, click-outside dismissal, and a working chat demo so you can see it inserting into a real conversation rather than an empty textarea.

Inserting at the cursor, not the end

The naive approach — input.value += emoji — always appends to the end, which breaks the moment someone clicks back into the middle of a sentence to fix a typo and then wants to drop in an emoji there. insertEmoji(e) instead reads inp.selectionStart, the cursor's character offset, and rebuilds the string around it: inp.value.slice(0, pos) + e + inp.value.slice(pos). The subtle part is what happens *after*: setting .value programmatically silently resets the cursor to position zero, so the very next line restores it with inp.selectionStart = inp.selectionEnd = pos + e.length — placing the caret immediately after the freshly inserted emoji, exactly where a native keyboard insertion would leave it. Skip that one line and every emoji after the first lands in the wrong place.

A "recently used" tab that's really a tiny LRU cache

The Recent tab isn't hand-curated — it's a 12-item array that rebuilds itself on every selection: recent = [e, ...recent.filter(x => x !== e)].slice(0, 12). Unshifting the new emoji to the front, filtering out any earlier occurrence of the same one, and slicing to 12 is the textbook shape of a least-recently-used cache, just applied to emoji instead of memory pages. It's the same "move to front, evict the tail" idea that powers browser history and tab-switcher ordering — useful to recognise because it shows up anywhere a UI claims to remember "recent" anything.

One render function, six categories

Rather than writing six near-identical grid-building blocks, renderGrid(emojis) takes any flat array of emoji strings, clears #emoji-grid, and stamps out a button per emoji with its click handler wired to insertEmoji. showCat(btn, cat) just decides *which* array to hand it — recent for the clock tab, EMOJI_DATA[cat] for everything else — toggles the .active class, and clears any in-progress search. Adding a seventh category is a one-line addition to EMOJI_DATA plus a matching .cat-tab button; the rendering plumbing needs no changes at all.

Closing on an outside click

A single listener on document checks every click against e.target.closest('#emoji-wrap'). If the click landed anywhere outside the trigger-and-popover wrapper, togglePicker() fires and the panel closes — the same delegation pattern used by the Command Palette and Context Menu snippets to dismiss themselves without attaching a listener to every possible "outside" element. The popover itself animates with opacity, pointer-events, and a scale(0.96) → scale(1) transform — properties the compositor can animate without ever triggering layout.

Where the search currently falls short

The filterEmoji(q) stub is honest about its limits: without a names database, "smile" can't be matched to 😀, so it currently just shows a broad slice of every category on any non-empty query. The FAQ below shows how a small emoji-datasource-style package turns this into real name-based search — the kind that lets someone type "fire" and land on 🔥 instead of scrolling six tabs.

Step by step

How to Use

  1. 1
    Click the 😊 button to open the emoji pickerThe picker slides open with a scale animation. Browse categories with the tab buttons. Click any emoji to insert it at the cursor position in the text input.
  2. 2
    Click inside the text input first to set the cursor positionClick in the input where you want the emoji inserted, then open the picker and select an emoji. It inserts exactly at the cursor, not appended to the end.
  3. 3
    Type in the search bar to filter emojisThe search input filters the emoji display. Currently shows a broad set on any search — connect to an emoji names database (emoji-data npm package) for name-based search.
  4. 4
    Check the Recent tab to see your emoji historyThe Recent tab shows the 12 most recently used emojis, updated in real time as you select emojis from other categories. Click Send to send a chat message and test the full flow.
  5. 5
    Attach to any textarea or input in your projectChange document.getElementById("chat-input") to your own input element. The insertEmoji() function works with any text input or textarea by reading selectionStart.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component using useRef for the input, or "Tailwind" for a React + Tailwind CSS version.

Real-world uses

Common Use Cases

Chat app and messaging interface emoji insertion
The primary use case — a chat input with an emoji picker button. The cursor-position insertion works correctly even when the user has typed partway through a message and wants to insert an emoji in the middle.
Comment and reply system reaction picker
Social platforms and blog comment systems use emoji pickers for reactions and in-text emoji. The recent tab shows the user's favourite emojis for quick repeat use.
Social media post composer emoji support
Content creation tools need emoji pickers for post composers. The picker integrates with any textarea. Add a character counter alongside it to show the user how many characters their post uses including emojis.
Form field emoji support for display names and bios
Profile forms, username fields, and bio textareas often allow emojis. The picker provides a keyboard-free way to select emojis for users on desktop who cannot easily access the system emoji picker.
Study cursor-position text insertion technique
The insertEmoji() function demonstrates the correct pattern for inserting text at cursor position in any input: read selectionStart, slice and reassemble the string, restore cursor position. This pattern applies to any text insertion feature.
No-code builder and drag-drop editor emoji support
Visual page builders and no-code tools let users add emoji to text blocks. An embedded picker provides a better UX than asking users to use their system keyboard shortcut (Win+. or Cmd+Ctrl+Space) which many users do not know.
Related: Half-Star Rating Input
See the Half-Star Rating Input for a related forms pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

inp.selectionStart returns the current cursor position in the input field (an integer offset from the start). The insertion: inp.value = inp.value.slice(0, selectionStart) + emoji + inp.value.slice(selectionStart). After setting value, cursor is restored: inp.selectionStart = inp.selectionEnd = selectionStart + emoji.length. Without this, setting value programmatically resets the cursor to the end.

Import an emoji metadata package: import emojiData from "emoji-datasource". Each entry has char (the emoji) and name fields. In filterEmoji(q): const matches = emojiData.filter(e => e.name.toLowerCase().includes(q.toLowerCase())).map(e => e.char).slice(0,42). This gives real name-based search ("smiling face", "thumbs up") instead of the current category-level filtering.

Save to localStorage in insertEmoji(): localStorage.setItem("recent_emoji", JSON.stringify(recent)). On page load, restore: const saved = localStorage.getItem("recent_emoji"); if (saved) { try { recent = JSON.parse(saved); } catch {} }. This persists the user's recently used emojis so they appear in the Recent tab on the next visit.

Click "JSX" to download. Use useState for isOpen, curCat, and recent. Use useRef for the input element. In insertEmoji: const pos = inputRef.current.selectionStart; then use a React-controlled input value state with setInputValue(prev => prev.slice(0,pos) + e + prev.slice(pos)). For cursor restore after state update, use useEffect with a ref to the desired cursor position and set selectionStart/End after the render.