Icon Menu

A controlled popover menu for icon selection and upload.

Installation

pnpm add @notion-kit/icon-menu

Examples


With IconBlock

"use client";import { useState } from "react";import { IconBlock, type IconData } from "@notion-kit/icon-block";import { IconMenu } from "@notion-kit/icon-menu";const defaultIcon: IconData = { type: "text", src: "S" };export default function Default() {  const [icon, setIcon] = useState<IconData>(defaultIcon);  return (    <IconMenu      onSelect={setIcon}      onRemove={() => setIcon(defaultIcon)}      onUpload={(file) =>        setIcon({          type: "url",          src: URL.createObjectURL(file),        })      }    >      <IconBlock icon={icon} size="lg" />    </IconMenu>  );}

Notion Icons

Including all icon sources — emoji, lucide, and notion icons.

"use client";import { useState } from "react";import { IconBlock, type IconData } from "@notion-kit/icon-block";import {  IconMenu,  useEmojiFactory,  useLucideFactory,  useNotionIconsFactory,} from "@notion-kit/icon-menu";const defaultIcon: IconData = { type: "text", src: "S" };export default function NotionIcons() {  const [icon, setIcon] = useState<IconData>(defaultIcon);  const emoji = useEmojiFactory();  const lucide = useLucideFactory();  const notion = useNotionIconsFactory();  return (    <IconMenu      factories={[emoji, lucide, notion]}      onSelect={setIcon}      onRemove={() => setIcon(defaultIcon)}      onUpload={(file) =>        setIcon({ type: "url", src: URL.createObjectURL(file) })      }    >      <IconBlock icon={icon} size="lg" />    </IconMenu>  );}

Custom Factory

Use useCustomFactory to add your own icon sets.

"use client";import { useState } from "react";import { IconBlock, type IconData } from "@notion-kit/icon-block";import {  IconMenu,  useCustomFactory,  useEmojiFactory,} from "@notion-kit/icon-menu";const defaultIcon: IconData = { type: "text", src: "S" };const iconsData = [  {    id: "github",    name: "GitHub",    url: "https://cdn.simpleicons.org/github/white",    keywords: ["git", "code", "repo"],  },  {    id: "slack",    name: "Slack",    url: "https://cdn.simpleicons.org/slack",    keywords: ["chat", "messaging"],  },  {    id: "figma",    name: "Figma",    url: "https://cdn.simpleicons.org/figma",    keywords: ["design", "ui"],  },  {    id: "notion",    name: "Notion",    url: "https://cdn.simpleicons.org/notion/white",    keywords: ["wiki", "docs"],  },  {    id: "linear",    name: "Linear",    url: "https://cdn.simpleicons.org/linear",    keywords: ["project", "issues"],  },];export default function CustomFactory() {  const [icon, setIcon] = useState<IconData>(defaultIcon);  const emoji = useEmojiFactory();  const brands = useCustomFactory({    id: "brands",    label: "Brands",    icons: iconsData,  });  return (    <IconMenu      factories={[emoji, brands]}      onSelect={setIcon}      onRemove={() => setIcon(defaultIcon)}    >      <IconBlock icon={icon} size="lg" />    </IconMenu>  );}

Factories

Icon Menu uses a factory pattern that lets you compose any combination of icon sources.

FactoryDescription
useEmojiFactory()Built-in emoji picker with skin tone support.
useLucideFactory()Full Lucide icon set with color picking.
useNotionIconsFactory()Notion-style icons (outline/solid in 10 colors).
useCustomFactory()Bring your own icon set via an icons array.
useUploadFactory()Persists user-uploaded icons in localStorage.

API Reference

IconMenu

PropTypeDefaultDescription
factoriesIconFactoryResult[]-Icon factory hooks to use. Falls back to built-in defaults when omitted.
disabledboolean-Whether the menu is disabled.
onSelect(icon: IconData) => void-Handler that is called when an icon or emoji is selected or when a URL is submitted.
onUpload(file: File) => void-Handler that is called when an image file is submitted.
onRemove() => void-Handler that is called when the remove button is clicked.

Creating a Custom Factory

Use useCustomFactory to register your own icon set with the menu.

1. Define your icons

Each icon needs an id, name, url, and optional keywords for search:

const brands = useCustomFactory({
  id: "brands",
  label: "Brands",
  icons: [
    {
      id: "github",
      name: "GitHub",
      url: "https://cdn.simpleicons.org/github/white",
      keywords: ["git", "code", "repo"],
    },
    {
      id: "slack",
      name: "Slack",
      url: "https://cdn.simpleicons.org/slack",
      keywords: ["chat", "messaging"],
    },
  ],
});

2. Pass factories to IconMenu

Combine your factory with built-in ones and pass them via the factories prop:

const emoji = useEmojiFactory();
const brands = useCustomFactory({ ... });

<IconMenu
  factories={[emoji, brands]}
  onSelect={setIcon}
>
  <IconBlock icon={icon} size="lg" />
</IconMenu>

3. Upload factory

Use useUploadFactory to let users submit icons via URL or file upload. Uploaded icons are persisted in localStorage:

const emoji = useEmojiFactory();
const upload = useUploadFactory();

<IconMenu
  factories={[emoji, upload]}
  onSelect={setIcon}
  onUpload={(file) => setIcon({ type: "url", src: URL.createObjectURL(file) })}
>
  <IconBlock icon={icon} size="lg" />
</IconMenu>;

Factory options

OptionTypeDescription
idstringUnique ID for the factory tab.
labelstringTab label shown in the menu.
iconsCustomIcon[]Array of icon definitions.
recentLimitnumberMax recent icons to track (default: 20).