ContextMenuExtension

Powerful, extensible context menu system

Build rich, context-aware menus that appear on right-click. Perfect for content-specific actions like table operations, link editing, and custom workflows.

Right-Click MenusProvider SystemExtensibleCustomizable

Key Features

Everything you need for professional context menu experiences.

Provider-Based Architecture

Register multiple providers that can handle different content types and selection contexts with priority-based resolution.

Automatic Initialization

Smart initialization ordering ensures context menu providers register before dependent extensions, eliminating timing issues.

Headless & Themeable

Complete control over appearance with custom renderers, themes, and styles. Works with any design system.

Quick Start

Get context menus working in your editor with minimal setup.

Basic Setup

Import and add ContextMenuExtension

function MyEditor() {
  return (
    <DefaultTemplate
      extensions={[contextMenuExtension]}
      onReady={(editor) => {
        console.log('Editor with context menu ready!')
      }}
    />
  )
}
Table Integration

Enable context menus for table operations

import { TableExtension } from '@lexkit/editor/extensions'

// Configure table extension with context menu
const tableWithContextMenu = new TableExtension().configure({
  enableContextMenu: true, // Enable table context menus
  contextMenuItems: [
    {
      label: "Insert Row Above",
      action: () => commands.insertRowAbove(),
    },
    {
      label: "Insert Row Below",
      action: () => commands.insertRowBelow(),
    },
    {
      label: "Delete Row",
      action: () => commands.deleteRow(),
    }
  ]
})

const extensions = [
  contextMenuExtension, // Must come before table extension
  tableWithContextMenu
] as const

The ContextMenuExtension automatically handles initialization ordering, so table context menus work without manual array positioning.

Try ContextMenuExtension

Interactive demos showcasing different levels of context menu functionality.

Headless Context Menu

Basic context menu functionality without tables or other extensions

Right-click in the editor below to see the context menu
Right-click on text to see different context menu options!

Table Context Menu

Context menu with table support and improved theming

Right-click on tables and other content to see context menus! Try inserting a table first.

Advanced Context Menu

Custom providers for different content types with enhanced theming

Right-click on tables and text for different context menus
Try right-clicking on headings, tables, and regular text to see different context menus!

Provider System

The core of ContextMenuExtension - register providers for different content types.

Registering Custom Providers

Add context menu items for specific content

function MyEditor() {
  const { commands } = useEditor()

  useEffect(() => {
    // Register a custom context menu provider
    commands.registerProvider({
      id: 'my-custom-provider',
      priority: 50,
      canHandle: ({ target, selection }) => {
        // Check if we're in a paragraph
        return target.closest('p') !== null
      },
      getItems: ({ editor }) => [
        {
          label: 'Custom Action',
          action: () => {
            console.log('Custom action triggered!')
          }
        }
      ]
    })
  }, [commands])

  return <DefaultTemplate extensions={[contextMenuExtension]} />
}

Provider Properties:

  • id: Unique identifier for the provider
  • priority: Higher numbers checked first (default: 0)
  • canHandle: Function to determine if this provider should handle the context
  • getItems: Function returning menu items for this context
  • renderer: Optional custom renderer for this provider's menu

Customization

Complete control over appearance and behavior.

Custom Renderer

Create custom context menu styling

function CustomContextMenuRenderer(props) {
  const { items, position, onClose } = props

  return createPortal(
    <div
      className="my-custom-context-menu"
      style={{
        position: 'fixed',
        left: position.x,
        top: position.y,
        background: 'white',
        border: '1px solid #ccc',
        borderRadius: '8px',
        boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
        minWidth: '200px',
        zIndex: 9999
      }}
    >
      {items.map((item, index) => (
        <div
          key={index}
          className="menu-item"
          style={{
            padding: '12px 16px',
            cursor: 'pointer',
            borderBottom: index < items.length - 1 ? '1px solid #eee' : 'none'
          }}
          onClick={() => {
            item.action()
            onClose()
          }}
        >
          {item.label}
        </div>
      ))}
    </div>,
    document.body
  )
}

// Use custom renderer
const customContextMenu = contextMenuExtension.configure({
  defaultRenderer: CustomContextMenuRenderer
})
Configuration Options

Customize behavior and appearance

const customContextMenu = contextMenuExtension.configure({
  preventDefault: true, // Prevent browser context menu
  initPriority: 100,   // High priority for early registration
  theme: {
    container: 'my-context-menu',
    item: 'my-menu-item',
    itemDisabled: 'my-menu-item-disabled'
  },
  styles: {
    container: {
      backgroundColor: '#f8f9fa',
      borderRadius: '12px',
      boxShadow: '0 8px 32px rgba(0,0,0,0.1)'
    },
    item: {
      padding: '12px 16px',
      fontSize: '14px'
    }
  }
})

API Reference

Commands and state queries for programmatic control.

Commands
registerProvider(provider)

Register a context menu provider

unregisterProvider(id)

Remove a provider by ID

showContextMenu(config)

Show context menu programmatically

hideContextMenu()

Hide the current context menu

State Queries
isContextMenuOpen()

Check if context menu is visible

Programmatic Control

Control context menus programmatically

function MyToolbar() {
  const { commands, activeStates } = useEditor()

  const showCustomMenu = () => {
    commands.showContextMenu({
      items: [
        { label: 'Option 1', action: () => console.log('Option 1') },
        { label: 'Option 2', action: () => console.log('Option 2') }
      ],
      position: { x: 100, y: 100 }
    })
  }

  const hideMenu = () => {
    commands.hideContextMenu()
  }

  return (
    <div>
      <button onClick={showCustomMenu}>
        Show Custom Menu
      </button>
      <button onClick={hideMenu}>
        Hide Menu
      </button>
    </div>
  )
}

Best Practices

Tips for the best context menu experience.

Provider Priority

Use appropriate priority values. Higher priority providers (like tables) should have higher numbers to be checked first.

Initialization Order

The ContextMenuExtension uses initPriority to ensure it registers before dependent extensions, eliminating manual array ordering.

Performance

Keep canHandle functions lightweight. Avoid complex DOM queries or heavy computations in provider registration.

Accessibility

Ensure menu items have clear labels and consider keyboard navigation support for comprehensive accessibility.