Search in your picker
Rank emoji by meaning as people type, in any picker, with results on every keystroke.
On this page
Search is free on every plan. People type what they mean, and the picker shows the right emoji on the same keystroke. This guide shows the two ways to add it: a ready picker, or your own UI on top of the SDK.
Use a ready picker
If you do not have a picker yet, start with one of these. They show every emoji by category, switch to the Emojisense ranking as soon as someone types, and handle the keyboard, skin tones and loading for you.
- React and FrimousseA drop-in picker, or a shadcn/ui component.
- Web componentVue, Svelte, Angular and plain HTML.
- emoji-martKeep emoji-mart and add the ranking.
Search in your own picker
If you already have a picker, keep it and replace its search. Call the SDK on every keystroke and render the results. Dictionary results come back on the same keystroke. When the dictionary is unsure, meaning results arrive after a short pause and merge in.
import { useEmojiSearch, useEmojisense } from "@emojisense/react";
export function EmojiResults({ query }: { query: string }) {
const sense = useEmojisense({
packBaseUrl: "https://api.emojisense.com/v1/pack/0.1.0",
shardsUrl: "https://api.emojisense.com/p/0.1.0", // optional, layer 2: free
endpoint: "https://api.emojisense.com", // optional, layer 3: metered
publishableKey: "pk_live_…",
});
const { results, status, layer } = useEmojiSearch(query, sense, { limit: 24 });
return (
<ul role="listbox" aria-label="Emoji" aria-busy={status === "loading"}>
{results.map((result) => (
<li key={result.id} role="option" aria-selected={false}>
{result.emoji}
</li>
))}
</ul>
);
}import {
createEngine,
createLayeredSemantic,
createSearchSession,
loadPacks,
} from "emojisense";
const packs = await loadPacks({ baseUrl: "https://api.emojisense.com/v1/pack/0.1.0" });
const engine = createEngine(packs);
const session = createSearchSession({
engine,
// Shards first (free), then the API. Leave it out for fully offline search.
semantic: createLayeredSemantic({
shardsUrl: "https://api.emojisense.com/p/0.1.0",
endpoint: "https://api.emojisense.com",
key: "pk_live_…",
packVersion: engine.packVersion,
}),
limit: 24,
onChange: ({ results, status, layer }) => render(results, status, layer),
});
input.addEventListener("input", () => session.update(input.value));
// When the picker closes: session.dispose();The hosted API does not publish shards yet. With shardsUrl set, the SDK finds no shard index and asks the API, so the option is safe to keep. Each result is { emoji, id, score, source }. The id is the Emojibase hexcode of the base emoji, for example 1F996. The source is alias for the dictionary and semantic for meaning search. With custom emoji it can be custom, and with a culture file culture (see Culture layer).
States and layers
| status | Meaning |
|---|---|
idle | The query is empty. |
alias | Dictionary results only. The dictionary was confident, or no semantic layer had an answer. |
loading | Dictionary results are shown and a semantic request is on its way. |
fused | Meaning results are merged in. layer says which layer answered: shard or api. |
error | The semantic request failed. The dictionary results stay on screen. |
Never block the list on loading: the dictionary results are already good. Use it for a subtle hint at most.
Languages
Set a locale and the engine loads that language next to English and prefers it when ranking. People can still type English words.
// React: "es" loads the Spanish pack next to English.
const sense = useEmojisense({
packBaseUrl: "https://api.emojisense.com/v1/pack/0.1.0",
locale: "es",
});
// Any framework:
const packs = await loadPacks({
baseUrl: "https://api.emojisense.com/v1/pack/0.1.0",
locales: ["es"],
});
const engine = createEngine(packs);
engine.search("feliz cumpleaños", { locale: "es" });- “feliz cumpleaños” · es🥳 🎂 🎇
- “joyeux anniversaire” · fr🥳 🎂 🎈
- “生日快乐” · zh🎂 🎈 🎉
- “kolay gelsin” · tr💪 👷 👷♂️
Load more aliases when idle
Each language has a core part and an extension part. The core part is enough for the first search. The extension part adds more aliases and typos. useEmojisense and the web component load it for you when the browser is idle. With the engine alone, do it yourself:
import { createEngine, loadPacks } from "emojisense";
const core = await loadPacks({ baseUrl: "https://api.emojisense.com/v1/pack/0.1.0" });
let engine = createEngine(core); // search works now
requestIdleCallback(async () => {
const ext = await loadPacks({
baseUrl: "https://api.emojisense.com/v1/pack/0.1.0",
part: "ext",
});
engine = createEngine([...core, ...ext]); // more aliases and typos
});For example, with the core part “ship it” gives 🚀 🚢 🛳️, and with the extension part 🚀 📦️ 🚢.
Labels and accessibility
Give each result an accessible name. The engine has the emoji’s name in every loaded language:
const entry = engine.get(result.id);
const label = entry?.labels[locale] ?? entry?.labels.en ?? result.emoji;Render the results as an ARIA listbox with options, keep focus in the search input, and move the active option with aria-activedescendant. The ready pickers do all of this.
Skin tones
Results are base emoji. Apply the person’s tone when you show and insert them. The tones are none, light, medium-light, medium, medium-dark, dark.
import { applySkinTone } from "emojisense";
applySkinTone("👍", "medium"); // "👍🏽"
applySkinTone("🧑🤝🧑", "dark"); // "🧑🏿🤝🧑🏿"