Emoji Picker

An emoji picker with autocomplete, virtualized scrolling, and arrow key navigation.

Example
EmojiPicker.css
import {Autocomplete, GridLayout, ListBox, ListBoxItem, Select, SelectValue, Size, useFilter, Virtualizer} from 'react-aria-components';
import {Button} from './Button';
import {Popover} from './Popover';
import {SearchField} from './SearchField';
import _emojis from 'emojibase-data/en/compact.json';
import './EmojiPicker.css';

type Emoji = (typeof _emojis)[0];
const emojis: Emoji[] = _emojis.filter((e) => typeof e.label === 'string' && !e.label.startsWith('regional indicator'));

export default function EmojiPicker() {
  let {contains} = useFilter({ sensitivity: 'base' });

  return (
    <Select aria-label="Emoji" className="emoji-picker" defaultValue="🥳">
      <Button variant="secondary">
        <SelectValue />
      </Button>
      <Popover placement="bottom" className="emoji-picker-popover">
        <Autocomplete filter={contains}>
          <SearchField aria-label="Search" placeholder="Search emoji" autoFocus />
          <Virtualizer
            layout={GridLayout}
            layoutOptions={{
              minItemSize: new Size(32, 32),
              maxItemSize: new Size(32, 32),
              minSpace: new Size(4, 4),
              preserveAspectRatio: true,
            }}>
            <ListBox className="emoji-list" items={emojis} aria-label="Emoji list" layout="grid">
              {(item) => <EmojiItem id={String(item.unicode)} item={item} />}
            </ListBox>
          </Virtualizer>
        </Autocomplete>
      </Popover>
    </Select>
  );
}

function EmojiItem({ id, item }: { id: string; item: Emoji }) {
  return (
    <ListBoxItem
      id={id}
      value={item}
      textValue={(item.label || '') + (Array.isArray(item.tags) ? item.tags.join(' ') : '')}
      className="emoji-item">
      {item.unicode}
    </ListBoxItem>
  );
}

Components