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.
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.
Distance in pixels between the popover and the cursor.
CSS class name for styling the popover.
AutocompleteList
Container for autocomplete items. Handles keyboard navigation and filtering.
CSS class name for styling the list.
AutocompleteItem
An individual menu item.
The value used for filtering. When the user types after /, items are filtered based on whether their value matches.
Callback executed when the item is selected (clicked or Enter pressed).
CSS class name for styling the item.
AutocompleteEmpty
Displayed when no items match the current query.
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 = /\/(.*)$/
Strict Pattern (Recommended)
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:
Called when the menu opens or closes.
Called when the search query changes (text after the trigger).
Best Practices
-
Use semantic values: Include synonyms in the
value prop to improve searchability (e.g., "heading 1 h1" instead of just "heading 1")
-
Show keyboard shortcuts: Display markdown shortcuts (like
# for heading) to help users learn them
-
Group commands: Consider adding visual separators between command categories
-
Add icons: Include icons alongside labels for better visual recognition
-
Limit items: Don’t overwhelm users with too many options. 10-15 commands is a good starting point
See Also