UIComponentsEmoji PickerNew

Emoji Picker

Compact emoji panel for browsing and selecting emojis.

About

The emoji picker component is built using the Frimousse library built by Liveblocks.

Installation

CLI

npx shadcn@latest add "https://eo-n.vercel.app/r/emoji-picker"

Manual

Install the following dependencies:

npm install frimousse

Copy and paste the following code into your project.

"use client";
 
import * as React from "react";
import {
  EmojiPicker as EmojiPickerPrimitive,
  type EmojiPickerListCategoryHeaderProps,
  type EmojiPickerListEmojiProps,
  type EmojiPickerListProps,
  type EmojiPickerListRowProps,
  type EmojiPickerRootProps,
  type EmojiPickerSearchProps,
  type EmojiPickerSkinToneProps,
  type EmojiPickerSkinToneSelectorProps,
  type EmojiPickerViewportProps,
} from "frimousse";
import { LoaderIcon, SearchIcon } from "lucide-react";
 
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
 
function EmojiPicker({
  className,
  style,
  columns,
  ...props
}: EmojiPickerRootProps) {
  return (
    <EmojiPickerPrimitive.Root
      data-slot="emoji-picker"
      columns={columns}
      className={cn(
        "bg-card text-card-foreground isolate flex h-[368px] w-fit flex-col gap-1 overflow-hidden rounded-lg border p-2 group-[[data-slot=popover-content]]:border-none group-[[data-slot=popover-content]]:bg-transparent",
        className
      )}
      style={
        {
          "--columns": `${columns}`,
          ...style,
        } as React.CSSProperties
      }
      {...props}
    />
  );
}
 
function EmojiPickerSearch({ className, ...props }: EmojiPickerSearchProps) {
  return (
    <div
      data-slot="emoji-picker-search-wrapper"
      className="dark:bg-input/30 border-input relative z-10 flex h-9 w-full min-w-0 flex-none items-center gap-2 rounded-md border bg-transparent py-1 pr-2.5 pl-7.5 transition-[color,box-shadow]"
    >
      <SearchIcon className="text-muted-foreground pointer-events-none absolute top-1/2 left-2 size-4 shrink-0 -translate-y-1/2" />
      <EmojiPickerPrimitive.Search
        data-slot="emoji-picker-search"
        className={cn(
          "placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground focusable size-full appearance-none text-base outline-none disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
          className
        )}
        {...props}
      />
    </div>
  );
}
 
function EmojiPickerContent({
  className,
  children,
  ...props
}: EmojiPickerViewportProps) {
  return (
    <EmojiPickerPrimitive.Viewport
      data-slot="emoji-picker-content"
      className={cn("relative flex-1 outline-hidden", className)}
      {...props}
    >
      <EmojiPickerPrimitive.Loading
        data-slot="emoji-picker-loading"
        className="text-muted-foreground absolute inset-0 flex items-center justify-center"
      >
        <LoaderIcon className="pointer-events-none size-4 shrink-0 animate-spin" />
      </EmojiPickerPrimitive.Loading>
      <EmojiPickerPrimitive.Empty
        data-slot="emoji-picker-empty"
        className="text-muted-foreground absolute inset-0 flex items-center justify-center text-sm"
      >
        No emojis found
      </EmojiPickerPrimitive.Empty>
      {children}
      <EmojiPickerList />
    </EmojiPickerPrimitive.Viewport>
  );
}
 
function EmojiPickerList({
  className,
  children,
  ...props
}: EmojiPickerListProps) {
  return (
    <EmojiPickerPrimitive.List
      data-slot="emoji-picker-list"
      className={cn("w-fit pb-1.5 select-none", className)}
      components={{
        CategoryHeader: EmojiPickerCategoryHeader,
        Row: EmojiPickerRow,
        Emoji: EmojiPickerEmoji,
      }}
      {...props}
    >
      {children}
    </EmojiPickerPrimitive.List>
  );
}
 
function EmojiPickerSkinToneSelector({
  className,
  emoji = "👋",
  ...props
}: EmojiPickerSkinToneSelectorProps) {
  return (
    <EmojiPickerPrimitive.SkinToneSelector
      data-slot="emoji-picker-skin-tone-selector"
      className={cn(
        buttonVariants({
          variant: "outline",
          size: "icon",
          className: "text-lg",
        }),
        className
      )}
      title="Change skin tone"
      emoji={emoji}
      {...props}
    />
  );
}
 
function EmojiPickerCategoryHeader({
  category,
  className,
  ...props
}: EmojiPickerListCategoryHeaderProps) {
  return (
    <div
      data-slot="emoji-picker-category-header"
      className={cn(
        "bg-card text-muted-foreground p-1 text-xs font-medium",
        className
      )}
      {...props}
    >
      {category.label}
    </div>
  );
}
 
function EmojiPickerRow({
  children,
  className,
  ...props
}: EmojiPickerListRowProps) {
  return (
    <div
      data-slot="emoji-picker-row"
      className={cn("scroll-my-[2vw] sm:scroll-my-1.5", className)}
      {...props}
    >
      {children}
    </div>
  );
}
 
function EmojiPickerEmoji({
  emoji,
  className,
  ...props
}: EmojiPickerListEmojiProps) {
  return (
    <button
      data-slot="emoji-picker-emoji"
      className={cn(
        "data-[active]:bg-muted/80 data-[active]:border-border relative flex aspect-square max-w-[calc(100%/var(--frimousse-list-columns))] min-w-8 flex-1 items-center justify-center overflow-hidden rounded-md text-base whitespace-nowrap transition-colors duration-200 ease-out data-[active]:border sm:flex-none sm:text-lg",
        className
      )}
      {...props}
    >
      {emoji.emoji}
    </button>
  );
}
 
function EmojiPickerFooter({
  className,
  children,
  ...props
}: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="emoji-picker-footer"
      className={cn(
        "before:bg-border relative z-10 hidden min-h-11 w-full min-w-0 flex-none items-center gap-1 py-2 pb-0 before:absolute before:-inset-x-2 before:top-0 before:h-px before:w-[calc(100%_+_16px)] before:content-[''] sm:flex",
        className
      )}
      {...props}
    >
      <EmojiPickerPrimitive.ActiveEmoji>
        {({ emoji }) =>
          emoji ? (
            <>
              <div className="flex size-8 flex-none items-center justify-center text-xl">
                {emoji.emoji}
              </div>
              <span className="text-card-foreground truncate text-xs font-medium">
                {emoji.label}
              </span>
            </>
          ) : (
            <span className="text-muted-foreground ml-2 truncate text-xs font-medium">
              Select an emoji…
            </span>
          )
        }
      </EmojiPickerPrimitive.ActiveEmoji>
      <div className="ml-auto flex gap-1">{children}</div>
    </div>
  );
}
 
function EmojiPickerSkinTone({
  emoji = "👋",
  className,
  children,
  ...props
}: Omit<EmojiPickerSkinToneProps, "children"> & React.ComponentProps<"div">) {
  return (
    <div
      data-slot="emoji-picker-skin-tone"
      className={cn("bg-card flex items-center rounded-md", className)}
    >
      <EmojiPickerPrimitive.SkinTone emoji={emoji} {...props}>
        {({ setSkinTone, skinToneVariations, skinTone: activeSkinTone }) =>
          skinToneVariations.map(({ skinTone, emoji }) => (
            <button
              key={skinTone}
              onClick={() => setSkinTone(skinTone)}
              className={cn(
                buttonVariants({
                  variant: "ghost",
                  size: "icon",
                  className: "text-lg",
                }),
                skinTone === activeSkinTone && "bg-accent border"
              )}
            >
              {emoji}
            </button>
          ))
        }
      </EmojiPickerPrimitive.SkinTone>
      {children}
    </div>
  );
}
 
export {
  EmojiPicker,
  EmojiPickerSearch,
  EmojiPickerContent,
  EmojiPickerList,
  EmojiPickerSkinToneSelector,
  EmojiPickerFooter,
  EmojiPickerSkinTone,
};
Update the import paths to match your project setup.

Usage

Import all parts and piece them together.

import {
  EmojiPicker,
  EmojiPickerContent,
  EmojiPickerSearch,
} from "@/components/ui/emoji-picker";
<EmojiPicker
  onEmojiSelect={({ emoji }) => {
    console.log(emoji);
  }}
>
  <EmojiPickerSearch />
  <EmojiPickerContent />
</EmojiPicker>

Examples

On Popover

See the Frimousse docs for more information.

On this page