The value is the emoji sequence itself, with the skin tone applied, and it submits with a form under name. Typing filters by name and keywords, the arrow keys move through the grid and Enter picks. The grid is virtualized, so a full dataset of several thousand emoji stays fast. Without data the picker uses a compact English dataset that covers every standard category, and it hides emoji newer than the platform can draw. Country flags are hidden where the platform draws them as letters. Set emojiVersion to cap the versions yourself.
Every pick dispatches tp-action with the emoji and its record, even when the same emoji is picked again. A pick that changes the value also proposes a cancelable tp-value-change, followed by tp-value-commit. Cancelling tp-action leaves the value and the popup unchanged. Supply value for a controlled picker or defaultValue for an uncontrolled one; the mode stays fixed for the element’s lifetime. The same rule applies to the query, skinTone and open lanes.
Picks
Default density
Compact density
Restart from the initial state
Show the copyable source
picker="popup" renders a trigger Button showing the current emoji and opens the picker in a Popover. A pick closes it; cancel the tp-open-change with reason item-press to keep it open for several picks, as a message composer does. The trigger, Escape and outside presses still close it. Put your own trigger in the trigger slot to replace the Button.
Insert into a message
Default density
Compact density
Restart from the initial state
Show the copyable source
The skin tone Button in the footer opens a menu of tones. Emoji that accept a tone show it in the grid, the preview and their names, and picks carry it. default-skin-tone sets the starting tone: none, light, medium-light, medium, medium-dark or dark. The widget does not remember the choice, so store the tone from tp-skin-tone-change and pass it back next time; skin-tone makes it controlled.
Remembered tone
Default density
Compact density
Restart from the initial state
Show the copyable source
Set skin-tone-selector="false" to hide the Button when tones do not apply.
Recent emoji
recent is a property listing emoji to show in a Recent category at the top. The widget keeps no history of its own, so the application decides what counts as recent and where it is stored: per device, per account or per workspace. Entries the dataset does not know are skipped. The Recent category is hidden while searching.
Recent picks
Default density
Compact density
Restart from the initial state
Show the copyable source
custom takes groups of the application’s own image emoji, each with an id, label and emojis. Every custom emoji has an id, a label, optional keywords and an image src. Custom groups follow Recent and precede the dataset categories, they are searchable by label and keywords, and a custom pick publishes :id: as its value, which recent accepts too. icon sets the group’s category bar icon; without it the first image is used.
Workspace emoji
Default density
Compact density
Restart from the initial state
Show the copyable source
data takes a dataset, or a function that loads one per locale; the picker shows a loading state and a Retry action while a source loads or fails. A dataset lists categories in display order and emojis, each with its emoji, label, keywords and category. A small, curated set suits a reaction bar, together with category-bar="false", preview="false" and fewer columns. The picker calls a source after it connects, when data or locale changes and on reload(), aborting the previous call. Every status change dispatches tp-loading-status-change with { status, error }.
Curated reactions
Default density
Compact density
Restart from the initial state
Show the copyable source
The @tweakpad/ui/emoji entry point holds the dataset helpers. builtinEmojiData() returns the built-in dataset, getEmojiDetails() looks up the record of a sequence (toned or not) for showing a reaction’s name, and applySkinTone() and stripSkinTone() convert stored values. For the full Unicode set in another language, createEmojibaseSource() loads Emojibase data files from a URL you host, per locale, and caches them in local storage; createEmojibaseData() converts files you bundle yourself. The library never imports Emojibase itself. The locale comes from locale (or the document language) and falls back to English when the dataset does not publish it. Concurrent pickers share one load.
import {
createEmojibaseSource,
getEmojiDetails,
stripSkinTone,
} from '@tweakpad/ui/emoji';
picker.data = createEmojibaseSource({ url: '/emoji/emojibase' });
picker.locale = 'de';
// A stored reaction, shown with its name and without a tone.
const record = getEmojiDetails('👍🏽', picker.emojiData);
console.log(record?.label, stripSkinTone('👍🏽'));
Here the source reads the published emojibase-data package from a public CDN, which brings every Unicode emoji with its skin tones and search keywords. While the files load the picker shows its loading state, and if the request fails it offers Retry; later visits read from the cache.
Full Unicode set
Default density
Compact density
Restart from the initial state
Show the copyable source
The picker is a form control: the emoji submits under name, required validates, and Reset restores default-value. Inside a Field, the label names the picker and the Field shows its error.
Popup in a form
Default density
Compact density
Restart from the initial state
Show the copyable source
<form id="emoji-picker-form" style="display:grid;gap:var(--tp-space-4);max-inline-size:24rem;margin-inline:auto">
<tp-field label="Status emoji" description="Shown next to your name in the workspace.">
<tp-emoji-picker picker="popup" name="status" default-value="🏠" required></tp-emoji-picker>
</tp-field>
<div style="display:flex;gap:var(--tp-space-2)">
<tp-button type="submit">Save status</tp-button>
<tp-button type="reset" variant="outline">Reset</tp-button>
</div>
</form>
<output id="emoji-picker-form-output" style="display:block;margin-block-start:var(--tp-space-3);text-align:center;color:var(--tp-muted-foreground);font-size:var(--tp-text-sm)">Pick an emoji, then save.</output>
<script type="module">
const form = document.querySelector('#emoji-picker-form');
const output = document.querySelector('#emoji-picker-form-output');
form.addEventListener('submit', (event) => {
event.preventDefault();
output.textContent = `Status set to ${new FormData(form).get('status')}`;
});
</script>
Sizes and states
Small and read-only
Default density
Compact density
Restart from the initial state
Show the copyable source
readonly keeps focus, browsing and form submission but blocks picks; disabled removes all three. The list shows seven rows by default and fills a block size you set on the picker. Emoji glyphs use the --tp-font-emoji role, which you can point at an emoji web font.
Categories
The category bar is a row of icon tabs, one per category, underlined like variant="underline"
Tabs; a Tooltip names the tab under the pointer or focus. The selected category follows the top of
the list; pressing one scrolls its label to the top and highlights its first emoji, clearing an
active search, and it stays selected until the list scrolls or the highlight moves. scrollToCategory(id) does the same from code. Category labels stay at the top of the
list while their category scrolls; sticky="false" turns that off. category-bar="false" hides
the bar.
Keyboard
Focus stays in the search field while the highlight moves through the grid.
KeyActionDown, UpNext or previous row, keeping the column; the first emoji when nothing is highlightedLeft, RightPrevious or next emoji in the row, following the writing directionHome, EndFirst or last emojiEnterPick the highlighted emojiTabMove to the skin tone Button and the footerArrows in the category barMove between categories; Enter or Space jumpsEnter on the skin toneOpen the tone menu; arrows move, Enter chooses, Escape closes
Hovering an emoji with a mouse highlights it; pressing it picks it without moving focus.
Constituents and accessibility
The search field is a combobox named “Search emoji” that points its active descendant at the
highlighted emoji. The grid is a listbox named by label; each category is a group named by its
label, and each emoji is an option named by its name (and tone) with its position in the whole
list. A polite status announces the number of matches, loading and errors; tone changes are
announced. The category bar is a tab list named “Categories” whose tabs are named by their
categories. The skin tone Button is named by “Skin tone” and the current tone and opens a menu of
radio items with the current tone checked. The preview and custom images
are decorative. Only the rows near the visible part of the list are rendered, so large datasets
stay fast.
API Reference
Every element also accepts motion-policy.
Attributes
AttributePropertyTypeDefaultDescriptiondefault-valuedefaultValuestring | undefined—Starting emoji of an uncontrolled picker; form reset returns to it.queryquerystring—The search text that filters the emoji by name and keyword; setting it makes the search
controlled.default-querydefaultQuerystring | undefined—Starting search text of an uncontrolled search.skin-toneskinToneSkinTone—The skin tone applied to emoji that accept one; setting it makes the tone choice controlled.default-skin-tonedefaultSkinToneSkinTone | undefined—Starting skin tone of an uncontrolled tone choice.pickerpickerEmojiPickerPicker'inline'Renders the picker in place, or in a Popover opened from a trigger Button that shows the chosen
emoji.openopenboolean—Popup open state; the composed Popover remains the state owner.default-opendefaultOpenbooleanfalseOpens the popup on first render, for an uncontrolled popup picker.emoji-versionemojiVersionnumber | undefined—Highest Emoji version shown; detected from the platform when unset.columnscolumnsnumber9Number of emoji per row of the grid.stickystickybooleantrueKeeps each category's label at the top of the list while its emoji scroll.category-barcategoryBarbooleantrueShows the row of category tabs above the list.previewpreviewbooleantrueShows the footer that previews the highlighted emoji and its name.skin-tone-selectorskinToneSelectorbooleantrueShows the Button that opens the skin tone menu.sizesizeEmojiPickerSize'default'Scales the cells and the emoji in them.labellabelstring''Accessible name of the emoji list and of the popup trigger; a surrounding Field supplies it
otherwise.localelocalestring''Language of the emoji names and keywords, passed to a data source; the document language when
empty.
Properties
PropertyTypeDefaultDescriptiondataEmojiData | EmojiDataSource | undefined—A dataset or a data source; the built-in English dataset by default.recentreadonly string[][]Recent emoji supplied by the application; the widget keeps no history of its own.customreadonly CustomEmojiGroup[][]Your own image emoji, in categories of their own after the recent ones; a custom emoji's value
is :id:.stringsPartial<EmojiPickerStrings>{}Replaces built-in texts (search placeholder, status messages, tone names) for localization.onValueChangeEmojiPickerValueChangeCallback | undefined—Called synchronously with each tp-value-change proposal, before listeners; a controlled owner
writes value back here to accept it.onValueCommittedEmojiPickerValueCommitCallback | undefined—Called with each settled pick.emojiDataread-onlyEmojiData | null—The dataset after support filtering, or null before one loads.statusread-onlyEmojiPickerStatus—Loading state of the dataset; an error shows a Retry button.activeEmojiread-onlyEmojiPickerItem | null—The highlighted emoji, or null.activeCategoryread-onlystring | undefined—Identifier of the category at the top of the list; undefined while a query is active.controlledread-onlyboolean—Whether the value is owned by the application through value.supportedVersionread-onlynumber | undefined—Highest Emoji version shown: emojiVersion, else the detected one (undefined before).
Events
EventTypeDescriptiontp-actionCustomEvent<{ value: string; item: EmojiPickerItem; sourceEvent: Event; }>Cancelable, on every pick, also of the current emoji; detail.value, detail.item and detail.sourceEvent. Listen here to insert emoji into text; cancelling it leaves the value and the popup unchanged.tp-value-changeTpValueChangeEvent<string>Cancelable proposal of a different emoji; detail.metadata carries the picked item and its category.tp-value-commitTpValueCommitEvent<string>The settled emoji, right after an accepted pick.tp-query-changeTpValueChangeEvent<string>Cancelable search text proposal.tp-skin-tone-changeTpValueChangeEvent<"none" | "light" | "dark" | "medium-light" | "medium" | "medium-dark">Cancelable skin tone proposal from the tone menu.tp-loading-status-changeCustomEvent<{ status: EmojiPickerStatus; error: unknown; }>Each dataset loading status change; detail.status and detail.error.tp-open-changeTpOpenChangeEventCancelable proposal to open or close the popup; cancel the close after a pick to keep it open for several picks.tp-open-change-completeCustomEvent<{ open: boolean; }>After the popup finishes opening or closing; detail.open.tp-field-valueCustomEvent<{ value: string; previousValue: string; reason: ChangeReason; }>Each committed value, for a surrounding Field's validation state.
Methods
MethodReturnsDescriptionsetValue(value, reason, sourceEvent?)booleanProposes and commits a value; returns whether it was accepted.setOpen(open, reason, event?)booleanOpens or closes the popup picker through the composed Popover.close()voidCloses the popup picker.reload()voidCalls the data source again.scrollToCategory(id, reason, sourceEvent?)Promise<boolean>Scrolls a category's label to the top of the list and highlights its first emoji; an active
query is cleared first. Returns false for an unknown category.
Slots
SlotDescriptionfooterContent at the end of the footer, after the preview and the skin tone Button.triggerReplaces the popup picker's default trigger Button.
CSS parts
PartDescription::part(emoji-picker)The root: the category bar, the search field, the list and the footer.::part(emoji-picker-popup)The Popover holding the popup picker.::part(emoji-picker-retry)The Button that loads the dataset again after an error.::part(emoji-picker-trigger)The Button that opens the popup picker and shows the chosen emoji.