Emoji Picker
A searchable, category-tabbed emoji picker with skin-tone selection and recently-used memory, built on the emoji-mart dataset.
The EmojiPicker wraps a trigger of your choice in a popover. It is fully self-contained โ search, category tabs, a skin-tone selector, and a recently-used strip (persisted to localStorage) โ and returns the chosen emoji as native unicode so it drops straight into any text input, contentEditable, or message body. It powers the comment composer, email composer, and instant-message composer.
Installation
pnpm dlx @docyrus/cli add @docyrus/ui-emoji-pickerpnpm add @emoji-mart/datanpx shadcn@latest add button input popover scroll-areaUsage
import { EmojiPicker, type SelectedEmoji } from "@docyrus/ui/components/emoji-picker";
import { Button } from "@docyrus/ui/primitives/ui/button";
import { Smile } from "lucide-react";
function Composer() {
const insert = (emoji: SelectedEmoji) => {
console.log(emoji.native); // "๐"
console.log(emoji.shortcode); // ":smile:"
};
return (
<EmojiPicker side="top" align="start" onSelect={insert}>
<Button variant="outline" size="icon" aria-label="Add emoji">
<Smile className="size-4" />
</Button>
</EmojiPicker>
);
}The single child is the trigger (rendered via Radix asChild, so pass one focusable element). onSelect fires with the emoji already resolved for the active skin tone.
Bare panel
Use EmojiPickerPanel to embed the picker without the popover โ e.g. inside your own dropdown or a fixed side panel:
import { EmojiPickerPanel } from "@docyrus/ui/components/emoji-picker";
<EmojiPickerPanel onSelect={(emoji) => insert(emoji.native)} perLine={9} />Features
| Feature | Notes |
|---|---|
| Search | Matches emoji id, name, keywords, emoticons, and aliases (AND across terms). |
| Category tabs | Only the active category renders, so the popover stays light even across ~1,870 emoji. |
| Skin tone | Six-tone selector; the choice persists to localStorage (docyrus:emoji-skin-tone). |
| Recently used | Most-recent-first strip, persisted to localStorage (docyrus:emoji-recents). |
| Native output | onSelect returns unicode (native) plus the :shortcode: โ no sprite sheet needed. |
API Reference
EmojiPicker
| Prop | Type | Default |
|---|---|---|
children | ReactNode | โ (the trigger, asChild) |
onSelect | (emoji: SelectedEmoji) => void | โ |
defaultSkinTone | number | 0 |
perLine | number | 8 |
closeOnSelect | boolean | true |
align | 'start' | 'center' | 'end' | 'start' |
side | 'top' | 'right' | 'bottom' | 'left' | 'top' |
open | boolean | โ (controlled) |
onOpenChange | (open: boolean) => void | โ |
contentClassName | string | โ |
EmojiPickerPanel
| Prop | Type | Default |
|---|---|---|
onSelect | (emoji: SelectedEmoji) => void | โ |
defaultSkinTone | number | 0 |
perLine | number | 8 |
className | string | โ |
Type Exports
| Type | Description |
|---|---|
EmojiPickerProps | Props for EmojiPicker |
EmojiPickerPanelProps | Props for EmojiPickerPanel |
SelectedEmoji | { id, native, shortcode } โ the value passed to onSelect |
SelectedEmoji
| Field | Type | Description |
|---|---|---|
id | string | emoji-mart id, e.g. "smile" |
native | string | rendered unicode character (respects skin tone) |
shortcode | string | shortcode form, e.g. ":smile:" |
Helpers
The component also re-exports a few pure helpers from the emoji dataset:
| Export | Signature | Description |
|---|---|---|
searchEmojis | (query: string, limit?: number) => string[] | Emoji ids matching a query |
resolveNative | (id: string, skinTone: number) => string | Unicode char for an id + tone |
getShortcode | (id: string) => string | ":<id>:" |
Credits
Email Template Editor
A standalone, beehiiv-style email template editor built on PlateJS. A rich block canvas with a "+"/slash insert palette and merge-tag chips, a full Theme panel (Basic + Advanced) that live-styles every part of the email via CSS variables, email-safe HTML serialization, and a multi-provider preview simulator (Gmail / Outlook / Apple Mail / Mobile / Dark).
Enum Option Editor
Enum Option Editor component.