Skip to main content
The Slash Menu component displays a searchable list of commands when users type / in the editor. It’s built on top of the autocomplete system and provides quick access to insert blocks, change formatting, and execute editor commands.

Features

  • Triggered automatically by typing /
  • Fuzzy search/filtering of commands
  • Keyboard navigation (arrow keys, Enter, Escape)
  • Customizable command list
  • Empty state support
  • Framework-agnostic

Installation

import { AutocompletePopover, AutocompleteList } from 'prosekit/react/autocomplete'

Basic Usage

import { useEditor } from 'prosekit/react'
import {
  AutocompletePopover,
  AutocompleteList,
  AutocompleteItem,
  AutocompleteEmpty,
} from 'prosekit/react/autocomplete'

// Match "/" followed by any text
const regex = /\/([\w\s]*)$/

export default function SlashMenu() {
  const editor = useEditor()

  return (
    <AutocompletePopover regex={regex} className="slash-menu">
      <AutocompleteList>
        <AutocompleteItem value="paragraph" onSelect={() => 
          editor.commands.setParagraph()
        }>
          Text
        </AutocompleteItem>
        
        <AutocompleteItem value="heading 1" onSelect={() => 
          editor.commands.setHeading({ level: 1 })
        }>
          Heading 1
        </AutocompleteItem>
        
        <AutocompleteItem value="bullet list" onSelect={() => 
          editor.commands.wrapInList({ kind: 'bullet' })
        }>
          Bullet List
        </AutocompleteItem>
        
        <AutocompleteEmpty>
          No commands found
        </AutocompleteEmpty>
      </AutocompleteList>
    </AutocompletePopover>
  )
}

Components

AutocompletePopover

The container that handles positioning and visibility of the menu.
regex
RegExp
required
Regular expression to match the trigger pattern. The matched text is used for filtering.Example: /\/([\w\s]*)$/ matches ”/” followed by word characters or spaces.
placement
string
default:"bottom-start"
Where to position the popover relative to the cursor. Options: top, bottom, left, right, with modifiers like -start or -end.
offset
number
default:"4"
Distance in pixels between the popover and the cursor.
className
string
CSS class name for styling the popover.

AutocompleteList

Container for autocomplete items. Handles keyboard navigation and filtering.
className
string
CSS class name for styling the list.

AutocompleteItem

An individual menu item.
value
string
required
The value used for filtering. When the user types after /, items are filtered based on whether their value matches.
onSelect
() => void
required
Callback executed when the item is selected (clicked or Enter pressed).
className
string
CSS class name for styling the item.

AutocompleteEmpty

Displayed when no items match the current query.
className
string
CSS class name for styling the empty state.

Complete Example

import { useEditor } from 'prosekit/react'
import {
  AutocompletePopover,
  AutocompleteList,
  AutocompleteItem,
  AutocompleteEmpty,
} from 'prosekit/react/autocomplete'
import { canUseRegexLookbehind } from 'prosekit/core'

// Match "/" not preceded by non-whitespace
const regex = canUseRegexLookbehind() 
  ? /(?<!\S)\/([\w\s]*)$/u 
  : /\/([\w\s]*)$/u

export default function SlashMenu() {
  const editor = useEditor()

  const commands = [
    { label: 'Text', value: 'text', action: () => editor.commands.setParagraph() },
    { label: 'Heading 1', value: 'heading 1 h1', kbd: '#', action: () => editor.commands.setHeading({ level: 1 }) },
    { label: 'Heading 2', value: 'heading 2 h2', kbd: '##', action: () => editor.commands.setHeading({ level: 2 }) },
    { label: 'Heading 3', value: 'heading 3 h3', kbd: '###', action: () => editor.commands.setHeading({ level: 3 }) },
    { label: 'Bullet List', value: 'bullet list ul', kbd: '-', action: () => editor.commands.wrapInList({ kind: 'bullet' }) },
    { label: 'Ordered List', value: 'ordered list ol', kbd: '1.', action: () => editor.commands.wrapInList({ kind: 'ordered' }) },
    { label: 'Task List', value: 'task list todo checkbox', kbd: '[]', action: () => editor.commands.wrapInList({ kind: 'task' }) },
    { label: 'Quote', value: 'quote blockquote', kbd: '>', action: () => editor.commands.setBlockquote() },
    { label: 'Code', value: 'code block', kbd: '```', action: () => editor.commands.setCodeBlock() },
    { label: 'Divider', value: 'divider hr horizontal rule', kbd: '---', action: () => editor.commands.insertHorizontalRule() },
    { label: 'Table', value: 'table', action: () => editor.commands.insertTable({ row: 3, col: 3 }) },
  ]

  return (
    <AutocompletePopover regex={regex} className="slash-menu">
      <AutocompleteList>
        {commands.map((cmd) => (
          <AutocompleteItem
            key={cmd.value}
            value={cmd.value}
            onSelect={cmd.action}
            className="slash-menu-item"
          >
            <span>{cmd.label}</span>
            {cmd.kbd && <kbd>{cmd.kbd}</kbd>}
          </AutocompleteItem>
        ))}
        <AutocompleteEmpty className="slash-menu-empty">
          No commands found
        </AutocompleteEmpty>
      </AutocompleteList>
    </AutocompletePopover>
  )
}

Styling

.slash-menu {
  background: white;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  max-height: 320px;
  overflow-y: auto;
  padding: 4px;
}

.slash-menu-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.15s;
}

.slash-menu-item:hover,
.slash-menu-item[data-focused="true"] {
  background: #f3f4f6;
}

.slash-menu-item kbd {
  padding: 2px 6px;
  background: #f9fafb;
  border: 1px solid #e5e7eb;
  border-radius: 3px;
  font-size: 0.75rem;
  font-family: monospace;
}

.slash-menu-empty {
  padding: 16px;
  text-align: center;
  color: #9ca3af;
}

Regex Patterns

The regex prop controls when the menu appears and what text is used for filtering.

Basic Pattern

// Matches "/" followed by any text
const regex = /\/(.*)$/
import { canUseRegexLookbehind } from 'prosekit/core'

// Matches "/" only when not preceded by non-whitespace
// This prevents triggering in URLs or other contexts
const regex = canUseRegexLookbehind() 
  ? /(?<!\S)\/([\w\s]*)$/u 
  : /\/([\w\s]*)$/u

Custom Trigger

// Use "@" instead of "/"
const regex = /@([\w\s]*)$/

Keyboard Navigation

The slash menu supports these keyboard shortcuts by default:
  • Arrow Up/Down - Navigate through items
  • Enter - Select the focused item
  • Escape - Close the menu
  • Tab - Select and close (same as Enter)

Events

The AutocompletePopover emits events you can listen to:
onOpenChange
(open: boolean) => void
Called when the menu opens or closes.
onQueryChange
(query: string) => void
Called when the search query changes (text after the trigger).

Best Practices

  1. Use semantic values: Include synonyms in the value prop to improve searchability (e.g., "heading 1 h1" instead of just "heading 1")
  2. Show keyboard shortcuts: Display markdown shortcuts (like # for heading) to help users learn them
  3. Group commands: Consider adding visual separators between command categories
  4. Add icons: Include icons alongside labels for better visual recognition
  5. Limit items: Don’t overwhelm users with too many options. 10-15 commands is a good starting point

See Also