React and Frimousse
Hooks for any React picker, a drop-in Frimousse picker and a shadcn/ui component.
On this page
@emojisense/react has two hooks for any React picker, a drop-in picker built on Frimousse, and a shadcn/ui component. It works with React 18 and 19.
Install
npm install @emojisense/react frimoussepnpm add @emojisense/react frimousseyarn add @emojisense/react frimoussebun add @emojisense/react frimousseFrimousse is optional: leave it out if you only use the hooks.
Drop-in picker
EmojisensePicker keeps Frimousse’s browse view by category. While someone types, it shows the Emojisense ranking as an ARIA listbox, with the same onEmojiSelect contract and skin tone. It can mount before the packs arrive: it shows Frimousse’s loading state and remounts when they are ready.
import { useEmojisense } from "@emojisense/react";
import { EmojisensePicker } from "@emojisense/react/frimousse";
export function EmojiPicker({ onPick }: { onPick: (emoji: string) => void }) {
const sense = useEmojisense({
packBaseUrl: "https://api.emojisense.com/v1/pack/0.1.0",
endpoint: "https://api.emojisense.com",
publishableKey: "pk_live_…",
});
return (
<EmojisensePicker
emojisense={sense}
onEmojiSelect={({ emoji }) => onPick(emoji)}
columns={9}
/>
);
}| Prop | Default | Notes |
|---|---|---|
emojisense | — | Required. The value of useEmojisense. |
onEmojiSelect | — | Required. Gets { emoji, label }, with the skin tone applied. A custom emoji has emoji :shortcode: and adds imageUrl and shortcode. |
placeholder | "Search emoji…" | Search input placeholder |
empty | — | Rendered when a query has no results |
limit | 24 | Number of ranked results |
columns | 9 | Emoji per row |
showRelevantNow | false | Show a row of seasonal and event emoji from the culture file above the browse list. Needs cultureUrl. |
relevantNowLabel | "Relevant now" | Heading of that row |
Every other Frimousse root prop passes through. Style the picker like any Frimousse picker. The ranked results add two hooks:
/* Frimousse is unstyled. The ranked results add these hooks: */
[data-emojisense-results] { gap: 0.25rem; }
[data-emojisense-results] [role="option"] { border-radius: 0.5rem; font-size: 1.5rem; }
[data-emojisense-results] [data-active] { background: #f0f0f4; }Hooks for your own picker
import { useEmojiSearch, useEmojisense } from "@emojisense/react";
const sense = useEmojisense({
packBaseUrl: "https://api.emojisense.com/v1/pack/0.1.0",
shardsUrl: "https://api.emojisense.com/p/0.1.0",
endpoint: "https://api.emojisense.com",
publishableKey: "pk_live_…",
});
const { results, status, layer } = useEmojiSearch(query, sense, { limit: 24 });useEmojisense(options)
Loads the packs once and builds the engine and the semantic layers.
| Option | Notes |
|---|---|
packBaseUrl | Required. The pack version directory. The core pack renders first. |
locale | Default "en". Another locale, such as "es", loads that pack next to English. |
shardsUrl | Precomputed results (layer 2), asked before the API. The hosted API does not publish shards yet; without a shard index the SDK asks the API. |
endpoint | The API (layer 3). Omit shardsUrl and endpoint for fully offline search. |
publishableKey | Your pk_live_… key for the API. |
extended | Default true: load the extension packs when the browser is idle. |
emojiSet | How the pickers draw emoji. "native" (default) uses the system font. "twemoji", "noto" and "fluent" draw images of a hosted set and need endpoint. |
customEmoji | Default false. true loads the app’s custom emoji from GET /v1/custom-pack, so they are searched on the device and drawn as images. Needs endpoint and publishableKey. |
tenant | Your id for one of your customers: adds that tenant’s custom emoji. |
cultureUrl | The culture files, for example https://api.emojisense.com/v1/culture/0.1.0. Emoji for the culture and the moment join the results after the top result, never above it (source: "culture"). Omit it for the canonical ranking. |
region | An ISO 3166-1 code such as "BR", for regional culture entries. Default: the region of the browser’s language, read on the device and never sent. "" = no region. |
It returns { engine, semantic, packs, customPack, locale, status, extended, emojiSet, endpoint, culture, region, error }. status is loading, ready or error. To draw one emoji the same way as the pickers in your own components, use <EmojiGlyph emoji={emoji} emojiSet={sense.emojiSet} endpoint={sense.endpoint} />.
useEmojiSearch(query, sense, options)
Searches as the query changes. Dictionary results update on every keystroke. Meaning results arrive after debounceMs (default 200) and merge in without moving confident hits. Options: limit (default 24), debounceMs and culture (false keeps the canonical ranking even when a culture file is loaded).
| Field | Meaning |
|---|---|
results | { emoji, id, score, source }[]. Custom emoji add imageUrl and shortcode; culture results add context and cultureId. |
status | idle, alias, loading, fused or error |
layer | Which layer produced the results: device, shard or api. undefined while idle or loading. |
alias | The on-device dictionary answer for the query, with its confidence, before fusion and culture |
aliasMs, semanticMs, semanticCached | Timings, for latency displays |
useRelevantNow(sense, options)
The emoji for an optional “relevant now” shelf: the featured seasonal and event entries of the culture file that are active on the device’s day, in the person’s region. It returns { emoji, hexcode, context, cultureId }[], empty without cultureUrl. Options: limit (default 8) and now.
shadcn/ui
The emoji-picker registry item is the Frimousse picker with Emojisense search, styled with Tailwind v4 theme tokens and your cn helper.
npx shadcn@latest add https://<registry-host>/r/emoji-picker.jsonThe command copies components/ui/emoji-picker.tsx and installs @emojisense/react, emojisense and frimousse.
import { useEmojisense } from "@emojisense/react";
import {
EmojiPicker,
EmojiPickerContent,
EmojiPickerFooter,
EmojiPickerSearch,
} from "@/components/ui/emoji-picker";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
export function ReactionButton({ onPick }: { onPick: (emoji: string) => void }) {
const sense = useEmojisense({ packBaseUrl, shardsUrl, endpoint });
return (
<Popover>
<PopoverTrigger>😀</PopoverTrigger>
<PopoverContent className="w-fit p-0">
<EmojiPicker
className="h-[21rem]"
emojisense={sense}
onEmojiSelect={({ emoji }) => onPick(emoji)}
>
<EmojiPickerSearch />
<EmojiPickerContent />
<EmojiPickerFooter />
</EmojiPicker>
</PopoverContent>
</Popover>
);
}| Part | Role |
|---|---|
EmojiPicker | Frimousse root. Props: emojisense, onEmojiSelect, columns (9), limit (36) and the Frimousse root props. |
EmojiPickerSearch | Combobox input. Arrow keys move through the results, Enter selects. |
EmojiPickerContent | Browse list for an empty query, ranked listbox for a typed query. empty sets the no-results text. |
EmojiPickerFooter | Active emoji preview and the skin tone selector |
To build and host the registry yourself, run pnpm --filter @emojisense/react build and serve packages/react/dist/r at /r/ on any static host.