Nasaq

EmojiPicker

Emoji picker in a popover, with search, categories, skin tones, RTL-aware keyboard navigation and lazily loaded emoji data.

PreviewOpen ↗

Code

import { EmojiPicker } from "@fadymondy/nasaq/web";import { useState } from "react";export function Composer() {  const [text, setText] = useState("");  return (    <div className="flex items-center gap-2">      <input value={text} onChange={(e) => setText(e.target.value)} aria-label="Message" />      <EmojiPicker onEmojiSelect={({ emoji }) => setText((t) => t + emoji)} />    </div>  );}

Pickers · beta

Live examples and controls: EmojiPicker in the lab.

Install

npx shadcn@latest add https://docs.nasaqui.com/r/emoji-picker.json

A styled emoji picker built on frimousse (headless, virtualised) and the Nasaq Popover. It gives you a search field, category headers, a grid and a skin tone button. Choosing an emoji calls onEmojiSelect and closes the popover.

Decision and bundle weight

The issue asked to evaluate the dependency first. [email protected]: 27 KB minified (9.8 KB gzip), zero dependencies, sideEffects: false (tree-shakable), peer react ^18 || ^19, TypeScript 5.1+. It does not bundle emoji data: the dataset (Emojibase) is fetched from the jsDelivr CDN the first time a picker opens and cached in the browser, so it adds nothing to your JS payload. Building our own picker would have needed the same lazy dataset plus virtualisation and keyboard handling, so we adopted it.

Limit: Emojibase has no Arabic dataset. In an Arabic UI the picker chrome (search placeholder, empty and loading text, skin tone label) is Arabic, but emoji names, category names and search keywords stay English. To use another dataset, pass resolveEmojiData (a frimousse option).

When to use

  • Reactions, chat composers, comment boxes, status emoji.

When not to use

  • Inserting text or mentions: use mention-textarea.
  • A fixed handful of reactions: render buttons yourself.

Import

import { EmojiPicker, EmojiPickerPanel } from "@fadymondy/nasaq/web";

Quick start

import { EmojiPicker } from "@fadymondy/nasaq/web";
import { useState } from "react";

export function Composer() {
  const [text, setText] = useState("");
  return (
    <div className="flex items-center gap-2">
      <input value={text} onChange={(e) => setText(e.target.value)} aria-label="Message" />
      <EmojiPicker onEmojiSelect={({ emoji }) => setText((t) => t + emoji)} />
    </div>
  );
}

Anatomy

EmojiPicker                         Popover + trigger
  PopoverContent
    EmojiPickerPanel                data-slot="emoji-picker"
      search                        data-slot="emoji-picker-search"
      skin tone button              data-slot="emoji-picker-skin-tone"
      viewport                      data-slot="emoji-picker-viewport"
        category header             data-slot="emoji-picker-category"
        row                         data-slot="emoji-picker-row"
          emoji                     data-slot="emoji-picker-emoji" (data-active on the highlighted one)

API

EmojiPicker

PropTypeDefaultDescription
onEmojiSelect(emoji: { emoji: string; label: string }) => voidCalled with the chosen emoji.
triggerReactElementicon Button with a smileThe element that opens the picker. Give it an accessible name.
closeOnSelectbooleantrueClose after choosing.
open / onOpenChangeboolean / (open: boolean) => voiduncontrolledControlled open state.
side / alignPopover placement"bottom" / "start"Logical, mirrors in RTL.
localeLocaleNasaq locale, else "en"Emoji-data locale.
skinTone"none" | "light" | "medium-light" | "medium" | "medium-dark" | "dark""none"Initial skin tone.
columnsnumber8Emoji per row. Match it with a className width.
labelsPartial<{ search; loading; empty(query); skinTone }>built-in en/arOverride strings.
classNamestringClasses for the panel, for example w-[22rem].

The other frimousse root props (emojiVersion, emojibaseUrl, resolveEmojiData, sticky) pass through.

EmojiPickerPanel

The same props without the popover ones (trigger, open, closeOnSelect, side, align). Use it inline or inside your own Popover.

resolveEmojiLocale(locale: string): Locale

Maps a UI locale to an available Emojibase locale ("ar" gives "en", "fr-CA" gives "fr").

Examples

Arabic composer:

import { EmojiPicker, Field, FieldLabel, Input } from "@fadymondy/nasaq/web";
import { useState } from "react";

export function ArabicComposer() {
  const [text, setText] = useState("");
  return (
    <div lang="ar" dir="rtl" className="flex items-end gap-2">
      <Field className="flex-1">
        <FieldLabel>الرسالة</FieldLabel>
        <Input value={text} onChange={(e) => setText(e.target.value)} />
      </Field>
      <EmojiPicker onEmojiSelect={({ emoji }) => setText((t) => t + emoji)} />
    </div>
  );
}

Custom trigger and fixed skin tone:

import { Button, EmojiPicker } from "@fadymondy/nasaq/web";

export function React() {
  return (
    <EmojiPicker skinTone="medium" trigger={<Button>React</Button>} onEmojiSelect={({ emoji }) => console.log(emoji)} />
  );
}

Accessibility

KeyAction
TypeFilter emoji in the search field.
Arrow keysMove the highlighted emoji. In RTL, ArrowLeft moves toward the visual left, which is the next emoji.
EnterChoose the highlighted emoji.
EscapeClose the popover.

The trigger, search field and skin tone button carry aria-labels (localised en/ar; override with labels). Give a custom trigger its own accessible name.

RTL & i18n

  • Category headers, search and layout use logical classes and follow dir.
  • frimousse's horizontal arrows are physical ("left = previous"); the panel swaps them when its computed direction is RTL.
  • Chrome strings come from the Nasaq locale (en, ar). Emoji data follows resolveEmojiLocale; Arabic falls back to English data (see the limit above).

Styling & tokens

Uses bg-popover, border-border, bg-card, text-muted-foreground, bg-nq-hover, outline-nq-focus. Target data-slot values from the Anatomy tree; the highlighted emoji has data-active. Extend with className.

Do / Don't

  • Do pass onEmojiSelect and insert at the caret in real inputs.
  • Do use locale for non-Arabic locales to get translated names.
  • Don't fetch emoji data yourself unless you supply resolveEmojiData.
  • Don't rely on emoji alone to convey meaning; pair with text.

Lab

https://docs.nasaqui.com/?path=/docs/components-pickers-emojipicker--docs

On this page