blokhaus

Slash Menu

Customize the slash command menu with custom items.

The slash menu is a command palette that opens when the user types / at the start of an empty paragraph. It provides quick access to every block type, media insertion, AI, and formatting option -- all searchable by name.

Setup

Add the SlashMenu component as a child of EditorRoot:

app/editor/page.tsx
import { EditorRoot, SlashMenu, InputRulePlugin } from "@blokhaus/core";

export default function EditorPage() {
  return (
    <EditorRoot
      namespace="my-editor"
      className="min-h-[400px] p-4 border rounded"
    >
      <SlashMenu />
      <InputRulePlugin />
    </EditorRoot>
  );
}

The InputRulePlugin is responsible for detecting the / trigger character. When the user types / at the start of an empty block, the plugin dispatches the OPEN_SLASH_MENU_COMMAND, which the SlashMenu component listens for.

The SlashMenuItem interface

Every item in the slash menu conforms to this interface:

interface SlashMenuItem {
  /** Unique identifier for the item */
  id: string;
  /** Display label shown in the menu */
  label: string;
  /** Short description shown alongside the label */
  description: string;
  /** Icon component rendered next to the label */
  icon: React.ComponentType<{ size?: number }>;
  /** Called when the item is selected */
  onSelect: () => void;
  /** Extra search terms for fuzzy matching (e.g., "h1" for "Heading 1") */
  keywords?: string[];
}

Default items

The slash menu ships with a comprehensive set of default items, organized by category:

AI

ItemDescriptionKeywords
Ask AIGenerate content with AI--

Headings

ItemDescriptionShortcut hintKeywords
Heading 1Large heading#h1
Heading 2Medium heading##h2
Heading 3Small heading###h3

Blocks

ItemDescriptionShortcut hint
QuoteBlockquote>
DividerHorizontal rule---
Code BlockSyntax highlighted code```
TableInsert a table--
CalloutHighlighted block with icon--
ToggleCollapsible content block--

Lists

ItemDescriptionShortcut hintKeywords
Bullet ListUnordered list---
Numbered ListOrdered list1.--
ChecklistTask list with checkboxes[]todo, task, checkbox, check

Media

ItemDescriptionKeywords
ImageUpload an image--
EmojiInsert an emojismiley, face, emoticon
VideoEmbed or upload a videoyoutube, vimeo, loom, embed, movie, clip

Format

ItemDescriptionKeywords
Font family itemsSwitch to a specific typefacefont, typeface, typography
Left to RightSet paragraph direction to LTRltr, direction, english, latin
Right to LeftSet paragraph direction to RTLrtl, direction, hebrew, arabic

Adding custom items

Pass additional items via the items prop. Custom items are appended after the default items:

import { SlashMenu } from "@blokhaus/core";
import type { SlashMenuItem } from "@blokhaus/core";
import { Wand2 } from "lucide-react";

const customItems: SlashMenuItem[] = [
  {
    id: "magic-block",
    label: "Magic Block",
    description: "Insert a custom magic block",
    icon: ({ size }) => <Wand2 size={size} />,
    keywords: ["magic", "custom", "special"],
    onSelect: () => {
      // Your custom logic here
      console.log("Magic block selected!");
    },
  },
];

function Editor() {
  return (
    <EditorRoot namespace="my-editor">
      <SlashMenu items={customItems} />
    </EditorRoot>
  );
}

Custom item with editor mutation

Most useful custom items will mutate the editor state. Access the editor instance via useLexicalComposerContext:

"use client";

import { useLexicalComposerContext } from "@blokhaus/core";
import {
  $getSelection,
  $isRangeSelection,
  $getRoot,
  $createParagraphNode,
  $createTextNode,
} from "lexical";

function useCustomSlashItems(): SlashMenuItem[] {
  const [editor] = useLexicalComposerContext();

  return [
    {
      id: "timestamp",
      label: "Timestamp",
      description: "Insert the current date and time",
      icon: ({ size }) => <ClockIcon size={size} />,
      keywords: ["date", "time", "now"],
      onSelect: () => {
        editor.update(() => {
          const selection = $getSelection();
          if (!$isRangeSelection(selection)) return;

          const timestamp = new Date().toLocaleString();
          const paragraph = $createParagraphNode();
          paragraph.append($createTextNode(timestamp));

          const anchor = selection.anchor.getNode();
          // Replace the "/" text node
          if (anchor instanceof TextNode) {
            anchor.remove();
          }
          // Insert the new node
          const root = $getRoot();
          root.append(paragraph);
          paragraph.selectEnd();
        });
      },
    },
  ];
}

As the user types after /, the menu filters items by matching the query against three fields:

  1. label -- the display name (e.g., "Heading 1")
  2. description -- the short description (e.g., "Large heading")
  3. keywords -- additional search terms (e.g., ["h1"])

The search is case-insensitive and uses substring matching. For example, typing /head matches "Heading 1", "Heading 2", and "Heading 3". Typing /h1 matches "Heading 1" via its keyword.

When no items match the query, the menu displays "No matching commands".

Keyboard navigation

The slash menu supports full keyboard navigation:

KeyAction
ArrowDownMove selection to the next item
ArrowUpMove selection to the previous item
EnterSelect the highlighted item
EscapeClose the menu without selecting

The selected item is highlighted with the --blokhaus-accent color and auto-scrolls into view when navigating with arrow keys.

Category grouping

When no search query is active, items are displayed in categorized sections with uppercase headers:

  • AI -- AI-powered features
  • Headings -- Heading levels
  • Blocks -- Structural block types (quotes, code, tables, callouts, toggles, dividers)
  • Lists -- List types (bullet, numbered, checklist)
  • Media -- Images, emoji, video
  • Format -- Font families, text direction
  • Other -- Any items that do not fit the above categories

When the user is filtering with a search query, category headers are hidden to maximize space.

Each category has a distinct icon color scheme for visual differentiation.

Opening the slash menu programmatically

You can open the slash menu from your own code by dispatching the command:

import { OPEN_SLASH_MENU_COMMAND } from "@blokhaus/core";

editor.dispatchCommand(OPEN_SLASH_MENU_COMMAND, undefined);

The menu positions itself at the current DOM selection's caret position.

Markdown shortcut hints

Several items display a markdown shortcut hint on the right side of the row. These hints show the equivalent markdown shortcut that can be typed directly (via InputRulePlugin) without opening the slash menu:

ItemShortcut hint
Heading 1#
Heading 2##
Heading 3###
Quote>
Bullet List-
Numbered List1.
Checklist[]
Divider---
Code Block```

Styling

The slash menu renders in a React Portal attached to document.body. It uses inline styles with CSS custom properties from the Blokhaus theme:

  • --blokhaus-popover-bg -- Background color
  • --blokhaus-popover-border -- Border color
  • --blokhaus-popover-shadow -- Box shadow
  • --blokhaus-accent -- Selected item background
  • --blokhaus-accent-foreground -- Selected item text color
  • --blokhaus-text-tertiary -- Category headers and shortcut hints
  • --blokhaus-separator -- Divider between sections
  • --blokhaus-muted -- Icon tile background

The menu has a frosted glass effect via backdrop-filter: blur(24px) saturate(190%) and a subtle entrance animation.

Props

PropTypeDefaultDescription
itemsSlashMenuItem[]undefinedAdditional items appended after the defaults