Emoji picker

A searchable grid of emoji with skin tones and recent picks.

Pick an emoji
🫥Pick an emoji
Installation
$Terminal
npx shadcn@latest add https://sdk-components.thesqd.com/r/emoji-picker.json
Usage
Render `EmojiPicker` inline, or `EmojiPickerPopover` behind a trigger. Pass `emojibaseUrl` to self-host the data.
TSImport
import { EmojiPicker, EmojiPickerPopover } from "@/components/ui/emoji-picker";
TSExample
import { EmojiPicker } from "@/components/ui/emoji-picker";

export function Example() {
  return <EmojiPicker onPick={({ emoji, label }) => console.log(emoji, label)} />;
}
$Phrase suggestions (optional)
npx shadcn@latest add https://sdk-components.thesqd.com/r/emo-suggester.json
Composition
Anatomy of the EmojiPicker primitive.
EmojiPicker                      (frimousse Root — data, virtualised grid, keyboard nav)
├── Input search                 (the visible query field)
├── Recent row                   (up to `columns` recent picks — Popular until there are any)
├── Viewport                     (nothing typed; scroll-fade)
│   ├── Loading → Spinner
│   └── List                     (sticky category headers, a ghost Button per emoji)
├── Search results               (something typed; ONE grid, scroll-fade, arrows + Enter)
│   ├── the `suggester`'s answers for the query read as a phrase, first
│   ├── then name and keyword matches, each emoji once
│   └── "No emoji match" only when both came back empty
└── Footer
    ├── Active emoji + label     (hovered or keyboard-selected)
    └── Skin tone Button         (cycles the five tones and none)

EmojiPickerPopover               (Popover + EmojiPicker variant="plain", closes on pick)
└── trigger={<Button … />}

Recent emoji and the skin tone are uncontrolled unless you pass
`recent` / `onRecentChange` and `skinTone` / `onSkinToneChange` —
store them in a user's settings to make them follow the person.
`addRecentEmoji(recent, emoji)` is exported for keeping your own list.

createEmoSuggester(options)      (emo-suggester.ts, a separate install)
└── → EmojiSuggester             { suggest(query, { skinTone, limit }) }
Inline
The panel on its own card. Hover or arrow through the grid; the footer names the active emoji.
Pick an emoji
🫥Pick an emoji
Phrase suggestions
Pass `suggester={createEmoSuggester()}` and a query is also read as a phrase — “pay my bills” suggests 💰 — by Emo, an on-device model from `emo-suggester`. It downloads on the first query (an ~18MB WebAssembly core plus an ~11MB model, cached). Free below 100k monthly active devices; credit Desert Ant Labs somewhere in your app (an About screen) and leave its anonymous usage count on.
Pick an emoji
🔎Try “pay my bills” or “taco tuesday”
Popover
`EmojiPickerPopover` puts the panel behind a trigger and closes on a pick.
Message input
A ghost icon trigger beside a field, appending the picked emoji to the text.
API Reference
Props exposed by EmojiPicker, EmojiPickerPopover and createEmoSuggester.

EmojiPicker

PropTypeDefaultDescription
onPick(emoji: { emoji: string; label: string }) => void—Called with the picked emoji, skin tone applied.
variant"card" | "plain""card"`card` draws its own card surface; `plain` draws none (inside a popover or panel).
columnsnumber8Cells per row. The panel's width follows it; 8 fits a 375px phone.
emojibaseUrlstringfrimousse's jsDelivr CDNWhere `{locale}/data.json` and `{locale}/messages.json` are served. Self-host when a CSP blocks the CDN.
recentstring[]—Recent emoji, newest first. Uncontrolled when omitted.
defaultRecentstring[]—Shown as a Popular row while there are no recent picks yet.
onRecentChange(recent: string[]) => void—Called with the new list after every pick.
skinTone"none" | "light" | "medium-light" | "medium" | "medium-dark" | "dark"—Uncontrolled (starting at `none`) when omitted.
onSkinToneChange(skinTone: SkinTone) => void—Called when the footer button cycles the tone.
suggesterEmojiSuggester—Reads the query as a phrase; its answers lead the search results, ahead of name and keyword matches. `createEmoSuggester()` is one; any `{ suggest }` works.
searchPlaceholderstring"Search emoji"Placeholder and accessible name of the search field.
autoFocusboolean—Focus the search field on mount.
classNamestring—Class names merged onto the root.

EmojiPickerPopover

PropTypeDefaultDescription
triggerReactElement—The element that opens the picker — usually a ghost icon Button.
align"start" | "center" | "end""start"Alignment against the trigger.
side"top" | "right" | "bottom" | "left"—Side of the trigger to open on.
…EmojiPickerProps—Every picker prop except `variant` and `autoFocus` (always `plain`, always focused).

createEmoSuggester(options)

PropTypeDefaultDescription
modelBaseUrlstringHugging FaceBase URL serving Emo's model files yourself.
litertWasmDirstringjsDelivrURL of the LiteRT.js `wasm/` directory.
accelerator"wasm" | "webgpu" | "webnn""wasm"LiteRT.js backend.
minConfidencenumber0.1Answers below this are dropped; the model answers even for gibberish.