Slash menu

Type / in an empty line to open a menu of blocks. Keep typing to filter, use the arrow keys to move and press Enter to apply.

Installation

This pattern is built from files in @intentui/editor. Install the editor, then copy the code above into a component of your own.

npx shadcn@latest add @intentui/editor

What's included

PieceWhy it is here
RichTextExtensionHeadings, quotes and paragraphs. Every pattern needs it.
ListExtensionNeeded by the numbered and bulleted list items.
CheckListExtensionNeeded by the check list item.
ComponentPickerRenders the menu and collects the items from its children.
ParagraphPickerPlugin, HeadingPickerPlugin, NumberedListPickerPlugin, BulletedListPickerPlugin, CheckListPickerPlugin, QuotePickerPluginOne item plugin per block. Remove a plugin to remove its items.

Item plugins must be rendered inside ComponentPicker. Outside it they throw an error. The demo uses a placeholder rather than initial content, so the menu is the first thing a user tries.

Items

Items appear in the order of the children. Search matches the label or any keyword.

ItemPluginKeywords
ParagraphParagraphPickerPluginnormal, paragraph, p, text
Heading 1, 2, 3HeadingPickerPluginheading, header, h1 / h2 / h3
Numbered listNumberedListPickerPluginnumbered list, ordered list, ol
Bulleted listBulletedListPickerPluginbulleted list, unordered list, ul
Check listCheckListPickerPlugincheck list, todo list, checkbox
QuoteQuotePickerPluginquote, block quote, blockquote

The full editor adds table, code block, divider, columns and image items. Each needs the extension of its block, see Insert block menu.

Trigger

The menu opens on / at the start of a line or after a space, and allows spaces in the query. It closes when nothing matches. Selecting an item removes the typed text and then runs the item.

Detailed menu

Set variant="detailed" to group the items into sections and show an icon tile, a label and a description for each one. The block editor uses it.

<ComponentPicker variant="detailed">
  <ParagraphPickerPlugin />
  <HeadingPickerPlugin />
  {/* ... */}
</ComponentPicker>

Every built-in item already carries a group and a description, and the default variant ignores them. Items are grouped by group in the order the plugins are rendered, so keep the plugins of one group next to each other. The built-in groups are:

GroupItems
Basic blocksParagraph, headings 1 to 3, bulleted list, numbered list, check list, quote, divider
MediaImage
AdvancedTable, code block, columns

To use your own sections or text, set group and description on your items. The default variant is unchanged, so editors that do not set variant look exactly as before.

Add your own item

Create a plugin that registers items with useComponentPickerItems. Keep the array stable with useMemo, as the existing plugins do.

components/editor/plugins/component-picker/my-picker-plugin.tsx
import { useMemo } from "react"

import {
  type ComponentPickerItem,
  useComponentPickerItems,
} from "@/components/editor/plugins/component-picker/component-picker-plugin"

export function MyPickerPlugin() {
  const items = useMemo<ComponentPickerItem[]>(
    () => [
      {
        value: "my-item",
        label: "My item",
        keywords: ["custom"],
        onSelect: () => {
          // runs after the typed "/query" text has been removed
        },
      },
    ],
    [],
  )

  useComponentPickerItems(items)
  return null
}

Then render <MyPickerPlugin /> inside ComponentPicker.

Styling

The list is a role="listbox" panel using bg-overlay, text-overlay-foreground, a ring border and w-56. The active row uses bg-accent text-accent-foreground and icons use text-muted-foreground. Edit component-picker-plugin.tsx to change the layout.

API

Fields of a ComponentPickerItem.
valuestring
Unique key for the item.
labelstring
Text shown in the menu and matched against the query.
iconReact.ReactNode
Optional icon shown before the label.
descriptionstring
Optional line shown under the label by the detailed variant.
groupstring
Optional section heading used by the detailed variant.
keywordsstring[]
Optional extra terms that match the query.
onSelect(queryString: string) => void
Called after the item is chosen, with the typed query.
Props of ComponentPicker.
childrenReact.ReactNode
The item plugins. Required.
variant"default" | "detailed"
detailed shows sections, an icon tile and a description for each item.
Default: "default"