Emoji Picker

A grouped, searchable grid with recents, rendered as text rather than sprites. One tab stop with arrow-key movement, and it never touches localStorage.

src/components/ui/emoji-picker.tsx

About

From the component's own source, where it was written.

A grid of emoji, grouped, searchable, with recents.

**The list is a prop with a small curated default, not all 3,700.** A full emoji set with keywords is roughly 200 kB of JSON before any images, and it has to be updated with every Unicode release. Most products need the reactions people actually use; if you need the whole set, pass it — the component does not care how long the array is.

**They are rendered as text, not images.** System emoji fonts are already installed, already the right style for the platform, scale as text, and cost nothing to load. Sprite sheets exist to make emoji look identical everywhere, which is a design goal worth having only if you are also willing to ship and maintain the sheet.

**Recents live with the caller.** This component never touches localStorage: a picker that writes to storage on its own cannot be server-rendered predictably, cannot be reset by the app, and puts a key in someone's browser they did not choose. recent and onSelect are enough for the caller to persist it wherever they already persist preferences.

The grid is one tab stop with arrow-key movement — a roving tabindex — because eight tab stops per row is unusable with a keyboard.

Install

npx astralyx-ui add emoji-picker

Usage

import { EmojiPicker } from '@/components/ui/emoji-picker'

<EmojiPicker recent={recent} onSelect={(emoji) => react(emoji.char)} />

Composer

Change the props and the source below follows.

Reacted with 🚀

Recent

Reactions

Smileys

Work

Life

Props
tsx
<EmojiPicker
  columns={8}
  recent={recent}
  onSelect={(emoji) => react(emoji.char)}
/>

Examples

Reacting, with recents

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

EmojiPicker

Extends Omit<ComponentProps<'div'>, 'onSelect'>.

PropTypeDefaultDescription
emojiEmoji[]COMMON_EMOJIA curated default is exported as COMMON_EMOJI. A full set with keywords is ~200 kB of JSON that needs updating every Unicode release; pass your own if you need it.
onSelect(emoji: Emoji) => void——
recentstring[][]Persisted by the caller. A picker that writes to localStorage itself cannot be server-rendered predictably and puts a key in a browser nobody chose.
recentLabelstring'Recent'—
columnsnumber8Grid width. Also the arrow-key row size.
searchablebooleantrue—
searchPlaceholderstring'Search emoji'—
emptyLabelstring'No emoji matches.'—
heightnumber240Height of the scrolling area.
labelstring'Emoji picker'—

Functions

FunctionSignatureDescription
COMMON_EMOJIEmoji[]A small, opinionated default: the ones that get used.

Types

Emoji

A grid of emoji, grouped, searchable, with recents.

FieldTypeDescription
charstring—
namestring—
groupstring—
keywords?string[]Extra search terms beyond the name.

Behaviour

TopicDetailDescription
text, not spritessystem fontsAlready installed, already right for the platform, scale as text, cost nothing to load. Sprite sheets buy identical rendering everywhere, at the cost of shipping and maintaining the sheet.
roving tabindexone tab stopArrow keys move within the grid, crossing group boundaries in reading order. Eight tab stops a row is unusable.