<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# EmojiPicker

An emoji picker for reactions and for putting emoji into text. It has a search field, a bar of categories, an optional row of quick reactions, a skin tone choice and a grid of the platform's own emoji, in a `Popover` that is a bottom sheet on phones. The app passes the emoji by category, with names and keywords in its own language, and the person's frequently used ones. With `catalogue` it offers an image catalogue's emoji instead (a standard set and an organization's custom emoji), drawn only as the catalogue's images, an animated one playing while it is in focus or under the mouse, and gives `onSelect` the emoji's id and the `:shortcode:` token to put into text. A catalogue's images are all fetched as the picker opens, the quick row and the grid's first eight rows first; while the browser asks to save data, the rest of the grid's wait until they near the screen.

```tsx
import { EmojiPicker } from "@/components/ouro/emoji-picker/emoji-picker";
```

## Example

```tsx
<EmojiPicker
  label="Add a reaction"
  trigger={<Button variant="plain" aria-label="Add a reaction"><SmileIcon /></Button>}
  categories={categories}
  frequent={frequent}
  quick={quickReactions}
  skinTone={tone}
  onSkinToneChange={setTone}
  onSelect={(selection) => react(message.id, selection.emoji)}
/>
<EmojiPicker
  label="Add a reaction"
  trigger={<Button variant="plain" aria-label="Add a reaction"><SmileIcon /></Button>}
  catalogue={catalogue}
  frequent={recentIds}
  quick={["like", "heart", "laugh"]}
  placeholder="/emoji/waiting.svg"
  onSelect={(selection) => react(message.id, selection.id)}
/>
```

## Usage

The search matches the start of any word in an emoji's name, keywords or shortcodes, ignoring case and accents, and announces how many it found. The grid is one Tab stop: the arrow keys move through it, Home and End go to the ends of a row (with Ctrl, of the grid), Page Down and Page Up go to the next and previous category, and Enter chooses. Every emoji is named for screen readers, and the one in focus shows large with its name under the grid. Choosing one calls `onSelect` and closes the picker; Escape closes it too.

A closed picker makes nothing from its emoji, so one in each conversation's reply box costs next to nothing to mount; the first to open makes what it shows from a catalogue, once for every picker given that catalogue.

## Use instead when

- Use `ReactionBar` instead to show a message's reactions and its few quick ones, with an EmojiPicker beside it for the rest.
- Use `Command` instead for a searchable list of actions.

## Props

The props the modes share. Type: `EmojiPickerProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `"start" \| "center" \| "end"` | `"center"` | How the popover lines up with the trigger along that side. |
| `categoriesLabel` | `string` | `"Categories"` | What screen readers hear for the category bar. |
| `className` | `string` |  | Layout only, applied to the panel inside the popover: width (`w-96`). Appearance classes warn in development. |
| `closeLabel` | `string` | `"Close"` | The label of the popover's close button, which only screen-reader and keyboard users see. |
| `frequentLabel` | `string` | `"Frequently used"` | The heading of the frequently used emoji. |
| `label` (required) | `string` |  | What the picker is for, which names it for screen readers: `"Add a reaction"`, `"Insert emoji"`. Required. |
| `noResultsLabel` | `string` | `"No emoji found"` | What shows, and is announced, when nothing matches the search. |
| `onSkinToneChange` | `((skinTone: EmojiSkinTone) => void)` |  | Called with the new skin tone when the person picks one, so the app can keep it. |
| `presentation` | `"popover" \| "auto" \| "sheet"` | `"auto"` | `auto`, the default, is a popover from 640px up and a bottom sheet below. `popover` stays beside the trigger at every width; `sheet` is always a bottom sheet. |
| `quickLabel` | `string` | `"Quick reactions"` | What screen readers hear for the `quick` row. |
| `resultsLabel` | `string` | `"{count} emoji"` | The heading of the search results, also announced as they change; `{count}` is the number found. |
| `searchLabel` | `string` | `"Search emoji"` | The search field's label and placeholder. |
| `side` | `"top" \| "bottom" \| "left" \| "right"` | `"bottom"` | The side of the trigger the popover sits on; it flips when there is no room. |
| `skinTone` | `"default" \| "light" \| "medium-light" \| "medium" \| "medium-dark" \| "dark"` |  | The skin tone the emoji show in, when you keep it (in the person's settings, say). Pair it with `onSkinToneChange`. Without it, the picker remembers the tone while it is mounted. |
| `skinToneLabel` | `string` | `"Skin tone"` | The name of the skin tone button and its choices. |
| `skinToneNames` | `Readonly<Record<EmojiSkinTone, string>>` | { default: "Default", light: "Light", "medium-light": "Medium-light", medium: "Medium", "medium-dark": "Medium-dark", dark: "Dark" } | The name of each skin tone, for other languages. |

## Modes

`EmojiPickerProps` is a union: pass the props of exactly one mode. Mixing them is a type error.

### Catalogue controlled: `EmojiPickerCatalogueControlledProps`

A controlled EmojiPicker of an image catalogue's emoji: you own `open`, update it in `onOpenChange`, and say what it points at with `anchor`.

| Prop | Type | Description |
| --- | --- | --- |
| `anchor` (required) | `RefObject<HTMLElement \| null>` | The element the picker points at, usually the button that toggles it: `anchor={buttonRef}`. |
| `catalogue` (required) | `EmojiCatalogue` | The emoji the picker offers, as images, by category, in order: a standard set, and an organization's custom emoji in a category of their own. The search matches their names, shortcodes and keywords. |
| `frequent` | `readonly string[]` | The ids of the person's frequently used or recent emoji, which the app keeps (a tone's id shows its emoji). Shown first, under `frequentLabel`. An id the catalogue no longer has is left out. |
| `onOpenChange` (required) | `(open: boolean) => void` | Called with `false` when the person dismisses the picker (Escape, an outside press, the close button) or chooses an emoji. |
| `onSelect` (required) | `(selection: EmojiPickerCatalogueSelection, event: MouseEvent<HTMLElement> \| KeyboardEvent<HTMLElement>) => void` | Called with the chosen emoji when the person presses one, or presses Enter in the search for its first result; the picker then closes. The event comes second, so the app can check `event.isTrusted` before it acts. |
| `open` (required) | `boolean` | Whether the picker is open. |
| `placeholder` | `string \| ReactElement` | Shows in each emoji's place until its picture has loaded and decoded, as `Emoji`'s `placeholder`: an image's address, or an element. The picker passes it to every emoji it draws: the grid, the quick row, the preview, the category buttons and the skin tones. Pass a value that stays the same between renders (an address, or an element made once), so the grid is not redrawn while the app renders. |
| `quick` | `readonly string[]` | The ids of a row of emoji above the categories for a single press, such as the quick reactions on a message. |

Not in this mode (typed `never`):

- `categories`: Not with `catalogue`. The emoji come from the catalogue: remove `categories`.
- `renderEmoji`: Not with `catalogue`. The picker draws the catalogue's images.
- `trigger`: Not with `open`. Remove `open`, `onOpenChange` and `anchor` to let a trigger open the picker.

### Catalogue with trigger: `EmojiPickerCatalogueWithTriggerProps`

An uncontrolled EmojiPicker of an image catalogue's emoji: `trigger` opens and closes it.

| Prop | Type | Description |
| --- | --- | --- |
| `catalogue` (required) | `EmojiCatalogue` | The emoji the picker offers, as images, by category, in order: a standard set, and an organization's custom emoji in a category of their own. The search matches their names, shortcodes and keywords. |
| `frequent` | `readonly string[]` | The ids of the person's frequently used or recent emoji, which the app keeps (a tone's id shows its emoji). Shown first, under `frequentLabel`. An id the catalogue no longer has is left out. |
| `onSelect` (required) | `(selection: EmojiPickerCatalogueSelection, event: MouseEvent<HTMLElement> \| KeyboardEvent<HTMLElement>) => void` | Called with the chosen emoji when the person presses one, or presses Enter in the search for its first result; the picker then closes. The event comes second, so the app can check `event.isTrusted` before it acts. |
| `placeholder` | `string \| ReactElement` | Shows in each emoji's place until its picture has loaded and decoded, as `Emoji`'s `placeholder`: an image's address, or an element. The picker passes it to every emoji it draws: the grid, the quick row, the preview, the category buttons and the skin tones. Pass a value that stays the same between renders (an address, or an element made once), so the grid is not redrawn while the app renders. |
| `quick` | `readonly string[]` | The ids of a row of emoji above the categories for a single press, such as the quick reactions on a message. |
| `trigger` (required) | `ReactElement` | The element that opens and closes the picker, usually an icon Button: `trigger={<Button aria-label="Add a reaction">…</Button>}`. |

Not in this mode (typed `never`):

- `anchor`: Not with `trigger`. The picker sits beside its trigger.
- `categories`: Not with `catalogue`. The emoji come from the catalogue: remove `categories`.
- `onOpenChange`: Not with `trigger`. Remove `trigger` to control the picker with `open`, `onOpenChange` and `anchor`.
- `open`: Not with `trigger`. Remove `trigger` to control the picker with `open`, `onOpenChange` and `anchor`.
- `renderEmoji`: Not with `catalogue`. The picker draws the catalogue's images.

### Controlled: `EmojiPickerControlledProps`

A controlled EmojiPicker of the platform's emoji: you own `open`, update it in `onOpenChange`, and say what it points at with `anchor`.

| Prop | Type | Description |
| --- | --- | --- |
| `anchor` (required) | `RefObject<HTMLElement \| null>` | The element the picker points at, usually the button that toggles it: `anchor={buttonRef}`. |
| `categories` (required) | `readonly EmojiPickerCategory[]` | Every emoji the picker offers, by category, in order. The app supplies them, so it picks the set, the names and the language. |
| `frequent` | `readonly EmojiPickerEmoji[]` | The person's frequently used emoji, which the app keeps. Shown first, under `frequentLabel`, with its own button in the category bar. |
| `onOpenChange` (required) | `(open: boolean) => void` | Called with `false` when the person dismisses the picker (Escape, an outside press, the close button) or chooses an emoji. |
| `onSelect` (required) | `(selection: EmojiPickerSelection, event: MouseEvent<HTMLElement> \| KeyboardEvent<HTMLElement>) => void` | Called with the chosen emoji when the person presses one, or presses Enter in the search for its first result; the picker then closes. The event comes second, so the app can check `event.isTrusted` before it acts. |
| `open` (required) | `boolean` | Whether the picker is open. |
| `quick` | `readonly EmojiPickerEmoji[]` | A row of emoji above the categories for a single press, such as the quick reactions on a message. |
| `renderEmoji` | `EmojiRender` | Draws an emoji in the grid, the quick row, the preview, the category buttons (each category's first emoji) and the skin tone choice (the first emoji with tones), for an app with its own emoji art: return an image 1em square, so it takes the size of the text, with an empty `alt` (the button already has the emoji's name). Pass a function that stays the same between renders, so moving focus redraws only the emoji it leaves and reaches. Without it, the picker shows the platform's own glyphs, in the skin tone in use. |

Not in this mode (typed `never`):

- `catalogue`: Not with `categories`. Remove `categories`, `frequent` and `quick` to offer an image catalogue's emoji instead.
- `placeholder`: Not with `categories`. A placeholder stands in for a catalogue's pictures; `renderEmoji` draws your own art.
- `trigger`: Not with `open`. Remove `open`, `onOpenChange` and `anchor` to let a trigger open the picker.

### With trigger: `EmojiPickerWithTriggerProps`

An uncontrolled EmojiPicker of the platform's emoji: `trigger` opens and closes it.

| Prop | Type | Description |
| --- | --- | --- |
| `categories` (required) | `readonly EmojiPickerCategory[]` | Every emoji the picker offers, by category, in order. The app supplies them, so it picks the set, the names and the language. |
| `frequent` | `readonly EmojiPickerEmoji[]` | The person's frequently used emoji, which the app keeps. Shown first, under `frequentLabel`, with its own button in the category bar. |
| `onSelect` (required) | `(selection: EmojiPickerSelection, event: MouseEvent<HTMLElement> \| KeyboardEvent<HTMLElement>) => void` | Called with the chosen emoji when the person presses one, or presses Enter in the search for its first result; the picker then closes. The event comes second, so the app can check `event.isTrusted` before it acts. |
| `quick` | `readonly EmojiPickerEmoji[]` | A row of emoji above the categories for a single press, such as the quick reactions on a message. |
| `renderEmoji` | `EmojiRender` | Draws an emoji in the grid, the quick row, the preview, the category buttons (each category's first emoji) and the skin tone choice (the first emoji with tones), for an app with its own emoji art: return an image 1em square, so it takes the size of the text, with an empty `alt` (the button already has the emoji's name). Pass a function that stays the same between renders, so moving focus redraws only the emoji it leaves and reaches. Without it, the picker shows the platform's own glyphs, in the skin tone in use. |
| `trigger` (required) | `ReactElement` | The element that opens and closes the picker, usually an icon Button: `trigger={<Button aria-label="Add a reaction">…</Button>}`. |

Not in this mode (typed `never`):

- `anchor`: Not with `trigger`. The picker sits beside its trigger.
- `catalogue`: Not with `categories`. Remove `categories`, `frequent` and `quick` to offer an image catalogue's emoji instead.
- `onOpenChange`: Not with `trigger`. Remove `trigger` to control the picker with `open`, `onOpenChange` and `anchor`.
- `open`: Not with `trigger`. Remove `trigger` to control the picker with `open`, `onOpenChange` and `anchor`.
- `placeholder`: Not with `categories`. A placeholder stands in for a catalogue's pictures; `renderEmoji` draws your own art.

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `data`: Not a prop. The emoji come by category in `categories`: `<EmojiPicker categories={[{ label, emoji }]}>`.
- `isOpen`: Not a prop. Ouro UI uses `open` with `onOpenChange`: `<EmojiPicker open={open} onOpenChange={setOpen} anchor={buttonRef}>`.
- `onClose`: Not a prop. `onOpenChange(false)` fires on close: `<EmojiPicker open={open} onOpenChange={setOpen} anchor={buttonRef}>`.
- `onEmojiClick`: Not a prop. A chosen emoji calls `onSelect`: `<EmojiPicker onSelect={(selection) => insert(selection.emoji)}>`.
- `onEmojiSelect`: Not a prop. A chosen emoji calls `onSelect`: `<EmojiPicker onSelect={(selection) => insert(selection.emoji)}>`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`). Your classes always win over Ouro UI's: EmojiPicker merges `className` last with `cn` (tailwind-merge), so a class that conflicts with its own replaces it.[^layout-classes]

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: use the `align` prop.

## Types

### `EmojiCatalogue`

An image-based emoji set, by category: what `EmojiPicker`, `ReactionBar`, `Autocomplete` and `EmojiText` draw instead of the platform's emoji font when an app passes it. Put an organization's custom emoji in a category of their own, labelled by the app. The image addresses are used as they are, so an app that loads images its own way (a content security policy, a custom scheme, signed or `blob:` URLs) puts the addresses it can load in the catalogue when it builds it. Ouro indexes a catalogue's emoji once, to find them in text. Build the catalogue once and pass that object, or give each copy the same `id`: a catalogue of new emoji objects on every render is indexed again each time, and warns in development. Adding or removing emoji is seen in a new catalogue object, even in the same array; to edit an emoji's shortcodes or text, pass a new emoji object, or a new `id`.

```ts
interface EmojiCatalogue {
  /**
   * Names this set of emoji, for an app that makes a new catalogue object, emoji and all, each
   * time it draws: catalogues with the same id share one index of their emoji, so change it when
   * the emoji change (`"contoso-2026-11"`), an emoji edited in place included. Without one, a
   * new catalogue object shares the index when it holds the same emoji objects in the same order,
   * and a catalogue of new emoji objects builds its own.
   */
  id?: string | undefined;
  /** The categories, in the order they show. An emoji in more than one counts once, by its id. */
  categories: readonly EmojiCatalogueCategory[];
}
```

### `EmojiCatalogueCategory`

A category of a catalogue: its name, the icon its button shows, and its emoji in order.

```ts
interface EmojiCatalogueCategory {
  /** Identifies the category, unique in the catalogue: `"people"`, `"custom"`. */
  id: string;
  /** Its name, shown as its heading and read for its button: `"People"`, or the organization's name for its custom emoji. */
  label: string;
  /** What its button in a picker's category bar shows: an icon element. Defaults to its first emoji's image. */
  icon?: ReactNode;
  /** Its emoji, in the order they show. */
  items: readonly EmojiItem[];
}
```

### `EmojiFilmstrip`

An animation drawn as a filmstrip: square frames stacked top to bottom in one image, so the frames are the image's height over its width.

```ts
interface EmojiFilmstrip {
  /** The filmstrip image's address. */
  src: string;
  /** Frames a second. */
  fps: number;
}
```

### `EmojiItem`

One emoji in an image catalogue: its id, its name and the image that draws it. An app maps its own emoji set to this, whether a standard set or an organization's custom emoji.

```ts
interface EmojiItem {
  /** Identifies the emoji, unique in the catalogue: `"party"`. Reactions, recents and `:id:` tokens use it. */
  id: string;
  /** Its name, which is the image's `alt`, what screen readers hear and what the search matches first: `"Party popper"`. */
  name: string;
  /**
   * The still image's address. It shows whenever the emoji is not playing, and always with reduced motion.
   */
  src: string;
  /** Sizes of the still image for sharper screens, as `<img srcset>`: `"party-2x.png 2x"`. */
  srcSet?: string | undefined;
  /**
   * An animated image (GIF, animated WebP or PNG), which plays while the pointer is on the emoji
   * or on the button around it, or that button has focus. With reduced motion it never plays.
   */
  animatedSrc?: string | undefined;
  /**
   * The animation as a filmstrip, square frames stacked top to bottom in one image, for an
   * emoji without `animatedSrc`: while the emoji plays, it steps through the frames at `fps`;
   * otherwise, and always with reduced motion, `src` shows.
   */
  filmstrip?: EmojiFilmstrip | undefined;
  /** The still image's width in pixels, given to the `<img>` so the page keeps its shape while it loads. It shows at the text's size. */
  width?: number | undefined;
  /** The still image's height in pixels, as `width`. */
  height?: number | undefined;
  /** Short names typed between colons, without them: `["party", "tada"]`. The first makes the emoji's token, `:party:`. */
  shortcodes?: readonly string[] | undefined;
  /** More words the search matches: `["celebrate", "hooray"]`. */
  keywords?: readonly string[] | undefined;
  /**
   * The emoji as plain text, such as its Unicode character (`"🎉"`), for places an image cannot
   * go (a copied message, a notification). `EmojiText` finds it in messages and draws the image;
   * Ouro never draws it as text.
   */
  text?: string | undefined;
  /** It is the organization's or app's own emoji rather than one of the standard set. Ouro draws it the same; apps can label or treat it apart. */
  custom?: boolean | undefined;
  /**
   * The emoji in Unicode's five skin tones, light to dark; leave it out for an emoji without tones.
   */
  skinTones?: readonly EmojiItemTone[] | undefined;
}
```

### `EmojiItemTone`

An emoji's image in one skin tone: `skinTones` on an `EmojiItem`.

```ts
interface EmojiItemTone {
  /** Identifies the emoji in this tone, unique in the catalogue: `"thumbs-up-tone3"`. Reactions and tokens use it. */
  id: string;
  /** The still image's address. */
  src: string;
  /** Sizes of the still image for sharper screens, as `<img srcset>`: `"wave-2x.png 2x"`. */
  srcSet?: string | undefined;
  /** An animated image (GIF, animated WebP or PNG), shown while the emoji plays; the still one shows otherwise. */
  animatedSrc?: string | undefined;
  /** The animation as a filmstrip of square frames, played while the emoji plays, for a tone without `animatedSrc`. */
  filmstrip?: EmojiFilmstrip | undefined;
  /**
   * The emoji in this tone as text (`"👍🏽"`), which `EmojiText` finds in messages; Ouro never draws it as text.
   */
  text?: string | undefined;
}
```

### `EmojiPickerCatalogueSelection`

What `onSelect` receives from a picker with a `catalogue`: the emoji chosen, in the skin tone it was chosen in.

```ts
interface EmojiPickerCatalogueSelection {
  /** The emoji's id, or its tone's id when it was chosen in a skin tone: `"thumbs-up-tone3"`. Keep it to react with, or among the person's frequently used emoji. */
  id: string;
  /** Its name: `"Thumbs up"`. */
  name: string;
  /**
   * The text to put into a message for it, `:shortcode:` (its first shortcode, else its id, and
   * in a tone the tone's id), which `EmojiText` draws as the image: `":party:"`.
   */
  token: string;
  /** The skin tone it was chosen in; `"default"` for an emoji without tones. */
  skinTone: EmojiSkinTone;
  /** The catalogue's emoji, as the app passed it. */
  item: EmojiItem;
}
```

### `EmojiPickerCategory`

A category of emoji: its name, the icon its button shows, and its emoji in order.

```ts
interface EmojiPickerCategory {
  /** The category's name, shown as its heading and read for its button: `"Smileys"`. Unique within the picker. */
  label: string;
  /** What its button in the category bar shows: an icon element or image. Defaults to its first emoji, drawn by `renderEmoji` when given. */
  icon?: ReactNode;
  /** The category's emoji, in the order they show. */
  emoji: readonly EmojiPickerEmoji[];
}
```

### `EmojiPickerEmoji`

One emoji the picker offers: the character, its name and the words that find it.

```ts
interface EmojiPickerEmoji {
  /** The emoji as text, drawn with the platform's own emoji font: `"👍"`. */
  emoji: string;
  /** Its name, which screen readers hear and the search matches: `"thumbs up"`. */
  name: string;
  /** More words the search matches: `["like", "yes", "approve"]`. */
  keywords?: readonly string[] | undefined;
  /**
   * It comes in skin tones. `true` puts Unicode's tone modifier after its first character
   * (right for one person or hand, such as 👍 or 🧑‍💻); for an emoji where that is not
   * right, such as two people holding hands, list its five toned forms, light to dark.
   */
  skinTones?: boolean | readonly string[] | undefined;
}
```

### `EmojiPickerSelection`

What `onSelect` receives: the emoji chosen, in the skin tone it was chosen in.

```ts
interface EmojiPickerSelection {
  /** The emoji as text, in the chosen skin tone when it has tones: `"👍🏽"`. */
  emoji: string;
  /** Its name: `"thumbs up"`. */
  name: string;
  /** The skin tone it was chosen in; `"default"` for an emoji without tones. */
  skinTone: EmojiSkinTone;
  /** The entry from `categories`, `frequent` or `quick` that was chosen, as the app passed it. */
  item: EmojiPickerEmoji;
}
```

### `EmojiRender`

Draws an emoji in the given skin tone: `renderEmoji`.

```ts
type EmojiRender = (item: EmojiPickerEmoji, skinTone: EmojiSkinTone) => ReactNode;
```

[^layout-classes]: test:registry/ouro/components/ouro/emoji-picker/emoji-picker.test.tsx::EmojiPicker opening and closing > lets app layout classes win on the panel verify:testExists(registry/ouro/components/ouro/emoji-picker/emoji-picker.test.tsx::EmojiPicker opening and closing > lets app layout classes win on the panel)
