A lightweight, unstyled, and composable emoji picker for React.
If you are using shadcn/ui, you can also install it as a pre-built component via the shadcn CLI.
Learn more in the shadcn/ui section.
Import the EmojiPicker parts and create your own component by composing them.
Apart from a few sizing and overflow defaults, the parts don’t have any styles out-of-the-box. Being composable, you can bring your own styles and apply them however you want: Tailwind CSS, CSS-in-JS, vanilla CSS via inline styles, classes, or by targeting the [frimousse-*] attributes present on each part.
You might want to use it in a popover rather than on its own. Frimousse only provides the emoji picker itself so if you don’t have a popover component in your app yet, there are several libraries available: Radix UI, Base UI, Headless UI, and React Aria, to name a few.
If you are using shadcn/ui, you can install a pre-built version of the component which integrates with the existing shadcn/ui variables via the shadcn CLI.
It can be composed and combined with other shadcn/ui components like Popover.
Use the resolveEmojiData prop to load emoji data from your own source or support locales Emojibase doesn’t provide. Return your data for the locales you handle and delegate the rest to defaultEmojiDataResolver.
The data should use the EmojiData type. For example, emoji-data.ts could contain:
Each emoji’s category matches a category’s index. Category labels and skinTones are localized names shown in the picker. Data must describe standard Unicode emojis rendered as text; image and sprite-based emojis aren’t supported.
Any string can be used as a locale. The default resolver falls back to en when Emojibase doesn’t support it. Frimousse uses custom data as-is; filter unsupported emoji versions and country flags before returning it if needed.
A resolver can return data or a promise. To cache custom data locally, use createEmojiDataCache.
Use useEmojiDetails to load a stored emoji’s localized label and search tags in a React component. It works without mounting a picker and shares its cached data with pickers using the same locale and source.
The component calling the hook must be inside a Suspense boundary. Loading errors reach the nearest error boundary; this example uses the react-error-boundary package.
Skin-tone variants return the base emoji’s details. Both ❤ and ❤️ match the same emoji too. If no match exists, the hook returns undefined after loading completes.
For a synchronous lookup, use getEmojiDetails. It reads data already loaded by a picker or the hook and returns undefined if that data isn’t loaded yet. It doesn’t load data or update your component when data becomes available.
By default, both APIs include emojis the browser can’t render.
Various styling-related details and examples.
The emoji picker doesn’t require hard-coded dimensions and instead supports dynamically adapting to the contents (e.g. the number of columns, the size of the rows, the padding within the sticky category headers, etc). One aspect to keep in mind is that inner components within EmojiPicker.List should be of the same size (e.g. all rows should be of the same height) to prevent layout shifts.
The --frimousse-viewport-width CSS variable can be used as a max-width to prevent some areas from becoming wider than the automatically sized contents, when showing the hovered emoji’s name below for example.
And although not required, it’s still possible to force the emoji picker and its contents to be of a specific width, to fit the viewport on mobile for example.
Because of its virtualized nature, adding padding to EmojiPicker.List can be tricky. We recommend adding horizontal padding to rows and category headers, and vertical padding on the list itself. Finally, to apply the same vertical padding to the viewport when keyboard navigating (which automatically scrolls to out-of-view rows), you can set the same value as scroll-margin-block on rows.
Some emoji pickers like Slack’s display their emoji buttons with seemingly random background colors when active (either hovered or selected via keyboard navigation). This can be achieved by using :nth-child selectors on rows and emojis to alternate through a list of colors. In the example below, a row’s first emoji has a red background, the second green, the third blue, then red again, and so on. All odd rows follow the same pattern, while even rows offset it by one to avoid every column using the same color, starting with blue instead of red.
Some other emoji pickers like Linear’s use the main color from the button’s emoji as background color instead. Extracting colors from emojis isn’t trivial, but a similar visual result can be achieved more easily by duplicating the emoji and scaling it to fill the background, then blurring it. In the example below, the blurred and duplicated emoji is built as a ::before pseudo-element.
All parts, hooks, and helpers, along their usage and options.
Surrounds all the emoji picker parts.
Options affecting the entire emoji picker are available on this component as props.
onEmojiSelect(emoji: Emoji) => voidA callback invoked when an emoji is selected.
localeLocaleDefault is "en"The locale of the emoji picker.
Any string is accepted. Locales not supported by Emojibase can be used with a custom resolveEmojiData.
skinToneSkinToneDefault is "none"The skin tone of the emoji picker.
columnsnumberDefault is 10The number of columns in the list.
stickybooleanDefault is trueWhether the category headers should be sticky.
emojiVersionnumberDefault is the most recent version supported by the current browserWhich Emoji version to use, to manually control which emojis are visible regardless of the current browser’s supported Emoji versions.
With a custom resolveEmojiData, this value is passed to the resolver, which is responsible for filtering the emojis.
emojibaseUrlstringDefault is "https://cdn.jsdelivr.net/npm/emojibase-data"The base URL of where the Emojibase data should be fetched from, used as follows: ${emojibaseUrl}/{locale}/{file}.json. (e.g. ${emojibaseUrl}/en/data.json).
The URL can be set to another CDN hosting the emojibase-data package and its raw JSON files, or to a self-hosted location. When self-hosting with a single locale (e.g. en), only that locale’s directory needs to be hosted instead of the entire package.
resolveEmojiDataEmojiDataResolverDefault is defaultEmojiDataResolverA function returning EmojiData or a promise for the current locale. It receives the locale along with { emojiVersion, emojibaseUrl, signal }.
Pass signal to fetch or other work that can be cancelled. It is aborted when the picker unmounts or needs to reload its data.
Runs on mount and when locale, emojiVersion, or emojibaseUrl changes. Changing the resolver function alone doesn’t reload the data. Custom data is used as-is, without filtering unsupported emoji versions or country flags.
By default, defaultEmojiDataResolver fetches Emojibase data from a CDN. Learn more in the custom data sources and locales section.
All built-in div props.
[frimousse-root]Can be targeted in CSS for styling.
[data-focused]Present when the emoji picker or its inner elements are focused.
--frimousse-emoji-font<string>A list of font families to use when rendering emojis.
--frimousse-viewport-width<length>The measured width of the viewport.
--frimousse-viewport-height<length>The measured height of the viewport.
--frimousse-row-height<length>The measured height of a row in the list.
--frimousse-category-header-height<length>The measured height of a category header in the list.
A search input to filter the list of emojis.
It can be controlled or uncontrolled.
All built-in input props.
[frimousse-search]Can be targeted in CSS for styling.
The scrolling container of the emoji picker.
All built-in div props.
[frimousse-viewport]Can be targeted in CSS for styling.
The list of emojis.
Inner components within the list can be customized via the components prop.
componentsPartial<EmojiPickerListComponents>The inner components of the list.
All built-in div props.
[frimousse-list]Can be targeted in CSS for styling.
CategoryHeaderEmojiPickerListCategoryHeaderPropsThe component used to render a sticky category header in the list.
All category headers should be of the same size.
[frimousse-category-header]Can be targeted in CSS for styling.
categoryCategoryThe category for this sticky header.
All built-in div props.
RowEmojiPickerListRowPropsThe component used to render a row of emojis in the list.
All rows should be of the same size.
[frimousse-row]Can be targeted in CSS for styling.
All built-in div props.
EmojiEmojiPickerListEmojiPropsThe component used to render an emoji button in the list.
All emojis should be of the same size.
[frimousse-emoji]Can be targeted in CSS for styling.
[data-active]Present when the emoji is currently active (either hovered or selected via keyboard navigation).
emojiEmoji & { isActive: boolean }The emoji for this button, its label, and whether the emoji is currently active (either hovered or selected via keyboard navigation).
All built-in button props.
Only renders when the emoji data is loading.
childrenReactNodeThe content to render when the emoji data is loading.
All built-in span props.
[frimousse-loading]Can be targeted in CSS for styling.
Only renders when no emoji is found for the current search.
It can also expose the current search via a render callback to build a more detailed empty state.
childrenReactNode | ((props: EmojiPickerEmptyRenderProps) => ReactNode)The content to render when no emoji is found for the current search, or a render callback which receives the current search value.
All built-in span props.
[frimousse-empty]Can be targeted in CSS for styling.
A button to change the current skin tone by cycling through the available skin tones.
The emoji used as visual can be customized.
If you want to build a custom skin tone selector, you can use the EmojiPicker.SkinTone component or the useSkinTone hook.
emojistringDefault is "✋"The emoji to use as visual for the skin tone variations.
All built-in button props.
[frimousse-skin-tone-selector]Can be targeted in CSS for styling.
Exposes the current skin tone and a function to change it via a render callback.
It can be used to build a custom skin tone selector: pass an emoji you want to use as visual and it will return its skin tone variations.
If you prefer to use a hook rather than a component, useSkinTone is also available.
An already-built skin tone selector is also available, EmojiPicker.SkinToneSelector.
emojistringDefault is "✋"The emoji to use as visual for the skin tone variations.
Exposes the currently active emoji (either hovered or selected via keyboard navigation) via a render callback.
It can be used to build a preview area next to the list.
If you prefer to use a hook rather than a component, useActiveEmoji is also available.
Returns the current skin tone and a function to change it.
It can be used to build a custom skin tone selector: pass an emoji you want to use as visual and it will return its skin tone variations.
If you prefer to use a component rather than a hook, EmojiPicker.SkinTone is also available.
An already-built skin tone selector is also available, EmojiPicker.SkinToneSelector.
emojistringDefault is "✋"The emoji to use as visual for the skin tone variations.
Returns the currently active emoji (either hovered or selected via keyboard navigation).
It can be used to build a preview area next to the list.
If you prefer to use a component rather than a hook, EmojiPicker.ActiveEmoji is also available.
Loads and caches Emojibase data, filtering out emojis the browser can’t render. It is the default resolver for EmojiPicker.Root and can be used for locales a custom resolver doesn’t handle.
localeLocaleRequiredThe locale to load. Falls back to en if Emojibase doesn’t support it.
optionsobjectRequiredAccepts emojiVersion and emojibaseUrl with the same behavior as the picker props, and an optional AbortSignal as signal to cancel loading. Pass an empty object to use the defaults.
Returns a promise of EmojiData, in the format shown in the custom data example. Loading errors reject the promise.
Creates a cache for storing custom emoji data in localStorage. Read and write entries inside your resolver to reuse data across visits. Use your own cache name to keep it separate from the default resolver’s data.
The JSON file uses the same format as the custom data example. Pass this resolveEmojiData function to EmojiPicker.Root. Entries stay cached until you replace or remove them; the cache doesn’t check for updates automatically.
options.namestringDefault is "frimousse/data"The cache name. Each entry is stored under its locale.
get(locale)Returns { data, metadata }, or null if the entry is missing or invalid.
set(locale, data, metadata?)Stores EmojiData and optional metadata for a locale, replacing any existing entry.
delete(locale)Removes the entry for a locale.
clear()Removes all entries with this cache name.
If browser storage is unavailable, reads return null and writes are ignored.
Synchronously reads an emoji’s localized label and search tags from data already loaded in memory by a picker or useEmojiDetails. It never fetches data, reads browser storage, or calls a resolver.
emojistringRequiredThe emoji to look up. Skin-tone variants return the base emoji’s details. Both ❤ and ❤️ match the same emoji.
options.localeLocaleDefault is "en"The locale to use. The default source falls back to en when Emojibase doesn’t support it.
options.emojibaseUrlstringDefault is "https://cdn.jsdelivr.net/npm/emojibase-data@latest"The base URL for Emojibase data, using the same directory structure as the picker’s emojibaseUrl prop.
options.emojiVersionnumberSelects the same versioned data source as the picker’s emojiVersion prop. An explicit emojibaseUrl takes precedence. Lookups don’t apply the picker’s browser-support filtering.
options.resolveEmojiDataEmojiDataResolverIdentifies a custom data source without calling it. Pass the same resolver function, locale, emojiVersion, and emojibaseUrl used by the picker or hook that loaded the data.
By default, data is shared with the picker and includes emojis the browser can’t render. A custom resolver determines which emojis are available for lookup.
Returns EmojiDetails, or undefined if the dataset hasn’t been loaded or no match exists. Use useEmojiDetails to load missing data and update a React component when it becomes available.
EmojiDetails has the following fields:
emojistringThe base emoji, without a skin tone.
labelstringThe localized name of the emoji.
tagsstring[]The localized search tags.
categorynumberMatches a category’s index in the source data.
versionnumberThe Emoji version that introduced this emoji.
countryFlagtrue | undefinedWhether the emoji is marked as a country flag.
skinsobject | undefinedSkin-tone variations, keyed by skin tone.
aliasesstring[] | undefinedOther emoji sequences that match this entry, such as mixed skin tones.
Returns an emoji’s localized details, suspending while data loads and throwing loading errors to the nearest error boundary. It works outside EmojiPicker.Root and shares cached data and pending requests with the default picker.
Accepts the same emoji and options as getEmojiDetails. Changing the locale or source loads the corresponding dataset; changing only the emoji reuses the loaded data. Requests continue if a suspended component unmounts or changes source, so other consumers can reuse their results.
A custom resolveEmojiData is called when its dataset isn’t cached. Define the resolver outside the component so its reference survives suspended renders; useCallback inside a component that suspends on its first render isn’t sufficient. Hooks using the same function, locale, emojiVersion, and emojibaseUrl share a request. The resolver receives the locale and those source options, without an AbortSignal.
Custom resolvers control persistent caching. Their completed data is shared with pickers and synchronous lookups using the same function and options. Return a new emojis array when its entries change.
Returns EmojiDetails, with the same fields and skin-tone behavior as getEmojiDetails, or undefined if no match exists in the loaded dataset. While loading, the nearest Suspense boundary displays its fallback. Server rendering also displays that fallback; data loads only on the client.
Loading failures are cached and thrown to the nearest error boundary. Resetting the boundary alone doesn’t retry the request. A successful load of the same source through a picker clears the cached failure; reset the boundary after that load, or reload the page to start fresh.
The name “frimousse” means “little face” in French, and it can also refer to smileys and emoticons.
The emoji picker component was originally created for the Liveblocks Comments default components, within @liveblocks/react-ui.
The emoji data is based on Emojibase.