Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Inputs

Emoji Picker

Inputs

Emoji Picker React component for Tailwind CSS v4

Email InputFile Upload
Live props
Layout
columns8
height296px
fullWidth
fullHeight
Appearance
size
radius
variant
tone
color
emojiFont
State
defaultValue
defaultSkinTone
Content
showRecent
maxRecent24
showSearch
showTabs
showSkinTones
showPreview
showSectionLabels
searchPlaceholder
Behavior
autoFocus
storageKey
locale
Code
import EmojiPicker from "@/components/ui/emoji-picker"

<EmojiPicker />
  1. Home
  2. Components
  3. Inputs
  4. Emoji Picker
Pro

Emoji Picker React component for Tailwind CSS v4

v1.0.0ยทAdded in 1.14.0

A React emoji picker with search, category tabs, recent emoji, skin tones and a preview, bundling 1,870 emoji with names and keywords.

Type in the search field and the grid narrows to matching emoji at once, best match first; Enter picks the first one and Escape clears the query. Click a category tab to jump to its section, and the tab under the scroll position lights up as you scroll. Hover an emoji to see it large in the footer with its name and shortcode. The hand button opens the six skin tones in the header row; the choice applies to every emoji that takes one and is remembered, like the recently used row at the top. Keyboard: Tab goes from the search to the skin tone button, the tabs (one stop, arrows move along) and the grid; in the grid the arrows move, Home and End go to the row ends, Page Up and Page Down jump five rows, Enter or Space picks, and typing any letter goes back to the search. Only the rows on screen are rendered. On a touch screen every cell is 44px, and EmojiPickerPopover opens as a bottom sheet the picker fills below 768px.

Emoji Picker is a Pro React component in the React input components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add emoji-picker, together with Button, Popover, Tooltip, useControllableState, useLocale and 5 more and the npm packages @tanstack/react-virtual, lucide-react and motion.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide ยท Next.js setup ยท Vite setup

npx wingo-ui@latest add emoji-picker

Emoji Picker is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import EmojiPicker from @/components/ui/emoji-picker.

import { useState } from "react"
import { EmojiPicker, EmojiPickerPopover } from "@/components/ui/emoji-picker"

export function CommentBox() {
  const [text, setText] = useState("Congrats on the launch, Emma ")
  return (
    <div className="flex flex-col gap-3">
      <div className="flex items-center gap-2">
        <input value={text} onChange={(event) => setText(event.target.value)} className="flex-1" />
        <EmojiPickerPopover onSelect={({ emoji }) => setText((current) => current + emoji)} />
      </div>
      <EmojiPicker columns={9} onSelect={({ emoji, name }) => console.log(emoji, name)} />
    </div>
  )
}

Props

  • columnsdefault 8
    number

    Cells per row. The picker is as wide as this many cells, and drops columns when its container is narrower.

  • sizedefault "md"
    "sm" | "md" | "lg"

    Cells of 32 / 36 / 44px with 20 / 24 / 28px emoji, and the search field (32 / 40 / 48px), tabs and footer to match. On touch screens every cell is at least 44px.

  • radiusdefault "md"
    "none" | "sm" | "md" | "lg" | "full"

    Corner shape of the cells, tabs, search field and skin tone button; scales with the size like the button's.

  • variantdefault "surface"
    "surface" | "plain"

    surface is a raised panel of its own; plain has no fill, for a popover, a sheet or a card that already has one.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    Color of the selected emoji's tint and ring and of the active category tab.

  • colordefault ""
    string

    Any CSS color in place of the tone. Leave empty to use the tone.

  • heightdefault 296
    number

    Height of the scrolling grid in px. Ignored with fullHeight.

  • fullWidthdefault false
    boolean

    Stretches to the container and fits as many columns as there is room for.

  • fullHeightdefault false
    boolean

    The grid takes the height left in the container instead of height. Give the picker or its parent a height.

  • value
    string

    The picked emoji, shown with a tint and ring in the grid. Pair it with onValueChange; leave it out to let the picker keep its own.

  • defaultValuedefault ""
    string

    The emoji shown as picked at first while uncontrolled.

  • onValueChange
    (emoji: string) => void

    Called with the emoji (skin tone applied) on every pick.

  • onSelect
    (emoji: EmojiPickerEmoji) => void

    Called on every pick with { emoji, base, name, keywords, category, skinTones }: the emoji with the skin tone, the one without, and its data.

  • skinTone
    "default" | "light" | "medium-light" | "medium" | "medium-dark" | "dark"

    Controlled skin tone; pair it with onSkinToneChange.

  • defaultSkinTonedefault "default"
    "default" | "light" | "medium-light" | "medium" | "medium-dark" | "dark"

    The skin tone until the person picks one. A saved choice (see storageKey) wins after the first render.

  • onSkinToneChange
    (tone: EmojiSkinTone) => void

    Called when a skin tone is picked from the header row.

  • categoriesdefault "["smileys", "people", "nature", "food", "activities", "travel", "objects", "symbols", "flags"]"
    EmojiCategoryId[]

    Which categories show, in this order: "smileys", "people", "nature", "food", "activities", "travel", "objects", "symbols", "flags". Search only looks in these.

  • showRecentdefault true
    boolean

    A recently used section (and tab) at the top, once something has been picked.

  • maxRecentdefault 24
    number

    How many recent emoji are kept, newest first.

  • recent
    string[]

    Controlled recent emoji (plain, without skin tone). Uncontrolled they are saved in localStorage under storageKey.

  • defaultRecent
    string[]

    The recent row before anything is picked or saved, like your app's favorites. The demo seeds 30 so maxRecent shows.

  • onRecentChange
    (recent: string[]) => void

    Called with the new recent list after a pick.

  • showSearchdefault true
    boolean

    The search field in the header.

  • showTabsdefault true
    boolean

    The row of category tabs. Each shows its name in a tooltip.

  • showSkinTonesdefault true
    boolean

    The hand button at the end of the header that opens the six skin tones.

  • showPreviewdefault true
    boolean

    The footer with the hovered or highlighted emoji, its name and its shortcode.

  • showSectionLabelsdefault true
    boolean

    A heading above each section in the grid.

  • searchPlaceholderdefault "Search emoji"
    string

    Placeholder of the search field.

  • autoFocusdefault false
    boolean

    Focuses the search field on mount. EmojiPickerPopover focuses it on open by itself (not on touch screens).

  • storageKeydefault "emoji-picker"
    string

    localStorage prefix for the recent emoji and the skin tone ("<key>:recent", "<key>:skin-tone"). An empty string keeps them in memory only.

  • emojiFontdefault ""Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", sans-serif"
    string

    The font stack the emoji are drawn with. The emoji are text, so no images load.

  • icons
    Partial<Record<"recent" | EmojiCategoryId, ReactNode>>

    Replaces the icons of the category tabs.

  • labels
    Partial<EmojiPickerLabels>

    Every string: search, clear, grid, categories, recent, results, resultCount ({count} results), noResults, noResultsHint, previewEmpty (Pick one of {count} emoji), skinTone, the nine category names and the six tone names. Defaults in EMOJI_PICKER_LABELS.

  • localedefault ""
    string

    Locale for lowercasing the query and formatting numbers: the emoji count in the empty preview (1,870 or 1.870) and the announced result count. Empty uses the UIProvider locale, then the browser's. The demo defaults to en-US.

  • classNames
    Partial<Record<"header" | "search" | "input" | "clear" | "skinTone" | "skinToneOption" | "tabs" | "tab" | "indicator" | "viewport" | "sectionLabel" | "row" | "cell" | "empty" | "footer" | "preview", string>>

    Extra classes for the inner parts.

  • className
    string

    Extra classes for the root, merged last. The root takes div props.

Parts

  • EmojiPickerPopover

    component

    A trigger plus the picker in a popover that becomes a bottom sheet below 768px, where the picker fills the sheet. Takes every EmojiPicker prop on top of its own.

    • trigger
      ReactNode

      The element that opens it, rendered asChild. Defaults to a ghost icon button with a smile.

    • triggerLabeldefault "Add emoji"
      string

      aria-label of the default trigger and the sheet's hidden title.

    • open
      boolean

      Controlled open state; pair it with onOpenChange.

    • defaultOpendefault false
      boolean

      Starting state while uncontrolled.

    • onOpenChange
      (open: boolean) => void

      Called when it opens or closes.

    • closeOnSelectdefault true
      boolean

      Closes after a pick; false keeps it open for several.

    • sidedefault "bottom"
      "top" | "right" | "bottom" | "left"

      Preferred side of the trigger; flips when there is no room.

    • aligndefault "start"
      "start" | "center" | "end"

      Alignment against the trigger.

    • sideOffsetdefault 8
      number

      Gap to the trigger in px.

    • responsivedefault true
      boolean

      A bottom sheet below 768px.

    • shortcut
      string

      A key combo in mod+k notation that toggles it from anywhere.

    • contentClassName
      string

      Classes for the floating panel or the sheet.

  • searchEmojis

    function

    The picker's search on its own: searchEmojis(query, { categories, skinTone, locale, limit }) returns EmojiPickerEmoji[], names before keywords, best match first. Handy for a :shortcode autocomplete.

  • applySkinTone

    function

    Returns the emoji with a skin tone, e.g. applySkinTone("๐Ÿ‘", "medium") gives ๐Ÿ‘๐Ÿฝ. Emoji without tones come back unchanged.

  • EMOJI_ROWS

    constant

    The bundled data (emoji-picker-data.ts): per category, rows of [emoji, name, keywords joined by |, skin tone positions], from Unicode Emoji 15.0 and the CLDR English annotations.

  • EMOJI_CATEGORIES

    constant

    The nine category ids in their default order.

  • EMOJI_SKIN_TONES

    constant

    The six skin tone ids, default first.

  • EMOJI_PICKER_LABELS

    constant

    The English defaults of labels; spread it to translate a few strings.

  • EmojiPickerEmoji

    type

    What onSelect receives: { emoji, base, name, keywords, category, skinTones }.

  • EmojiSkinTone

    type

    "default" | "light" | "medium-light" | "medium" | "medium-dark" | "dark".

  • EmojiCategoryId

    type

    One of the nine category ids.

Examples

In a message composer

EmojiPickerPopover next to the field. closeOnSelect off, so several picks go in a row. A bottom sheet on phones.

Reactions

A custom trigger after the reaction chips; a pick adds or toggles one. The info tone marks the selected emoji.

Sizes

sm with 32px cells and lg with 44px cells. On touch screens every size uses 44px.

Brand tone and skin tone

tone="brand", round cells and a medium skin tone applied to every hand and person.

Compact

Three categories, no tabs, skin tones or preview: search and the grid only.

Full width on a phone

fullWidth fits as many 44px columns as the 360px card allows.

Shortcode search

searchEmojis on its own powers a :shortcode autocomplete without the grid.

Dependencies

@tanstack/react-virtuallucide-reactmotionbuttonpopovertooltipuse-controllable-stateuse-localeuse-media-queryuse-persisted-statemotiontonesui-config

Files

  • components/ui/emoji-picker.tsx
  • components/ui/emoji-picker-data.ts

FAQ

Is the React Emoji Picker component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Emoji Picker?

Run npx wingo-ui@latest add emoji-picker. It also adds the Wingo UI items it builds on (Button, Popover, Tooltip, useControllableState, useLocale and 5 more) and the npm packages @tanstack/react-virtual, lucide-react and motion. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/emoji-picker.

How does Emoji Picker behave on phones?

On a touch screen every cell is 44px, and EmojiPickerPopover opens as a bottom sheet the picker fills below 768px.

See also

Emoji ReactionPopoverSearch InputVirtual ListusePersistedStatehook

More React input components

File UploadFilter BuilderInputInput GroupListboxMention Input
All 40 React input components

Articles using this component

  • React Emoji Picker: Search, Skin Tones and Phone Sheets

    Component guides ยท Nov 6, 2026 ยท 9 min read

PreviousEmail InputNext File Upload
Recently used
Smileys & emotion
Pick one of 1,870 emoji

Search, scroll or use the arrow keys, then pick one

Recent emoji are saved under emoji-picker:recent