Find out what's new in Tiptap Editor 3.0

AI Menu

Available in Start plan

A fully-featured AI-powered contextual menu for Tiptap editors. Provides intelligent content editing, generation, and transformation capabilities with floating menu positioning and customizable AI actions.

Installation

Add the component via the Tiptap CLI:

npx @tiptap/cli@latest add ai-menu

Components

<AiMenu />

A comprehensive AI-powered menu that provides contextual editing and generation capabilities.

Usage

import { EditorContent, EditorContext, useEditor } from '@tiptap/react'

// --- Tiptap Core Extensions ---
import { StarterKit } from '@tiptap/starter-kit'
import { Ai } from '@tiptap-pro/extension-ai'
import { UiState } from '@/components/tiptap-extension/ui-state-extension'

import { HorizontalRule } from '@/components/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension'
import { Selection } from '@tiptap/extensions'
import { AiProvider, useAi } from '@/components/contexts/ai-context'

// --- Tiptap UI ---
import { AiMenu } from '@/components/tiptap-ui/ai-menu'
import { AiAskButton } from '@/components/tiptap-ui/ai-ask-button'

// --- UI Primitive ---
import { ButtonGroup } from '@/components/tiptap-ui-primitive/button'

// --- Utils ---
import { TIPTAP_AI_APP_ID } from '@/lib/tiptap-collab-utils'

// --- Tiptap Node ---
import '@/components/tiptap-node/blockquote-node/blockquote-node.scss'
import '@/components/tiptap-node/code-block-node/code-block-node.scss'
import '@/components/tiptap-node/horizontal-rule-node/horizontal-rule-node.scss'
import '@/components/tiptap-node/heading-node/heading-node.scss'
import '@/components/tiptap-node/paragraph-node/paragraph-node.scss'

export const AiMenuExample = () => {
  return (
    <AiProvider>
      <AiEditorWrapper />
    </AiProvider>
  )
}

const AiEditorWrapper = () => {
  const { aiToken } = useAi()

  if (!aiToken) {
    return <div className="tiptap-editor-wrapper">Loading AI...</div>
  }

  return <AiEditor aiToken={aiToken} />
}

const AiEditor = ({ aiToken }: { aiToken: string }) => {
  const editor = useEditor({
    immediatelyRender: false,
    extensions: [
      StarterKit.configure({
        horizontalRule: false,
      }),
      HorizontalRule,
      Selection,
      UiState,
      Ai.configure({
        appId: TIPTAP_AI_APP_ID,
        token: aiToken,
        autocompletion: false,
        showDecorations: true,
        hideDecorationsOnStreamEnd: false,
        onLoading: (context) => {
          context.editor.commands.aiGenerationSetIsLoading(true)
          context.editor.commands.aiGenerationHasMessage(false)
        },
        onChunk: (context) => {
          context.editor.commands.aiGenerationSetIsLoading(true)
          context.editor.commands.aiGenerationHasMessage(true)
        },
        onSuccess: (context) => {
          const hasMessage = !!context.response
          context.editor.commands.aiGenerationSetIsLoading(false)
          context.editor.commands.aiGenerationHasMessage(hasMessage)
        },
      }),
    ],
    content: `
<p>Today, we're exploring how AI is transforming creative workflows. From writing assistance to intelligent summarization, the tools at our fingertips are evolving fast. But how do we use them responsibly?</p>
<p>In this article, we’ll look at real-world examples of AI enhancing—not replacing—human creativity.</p>
        `,
  })

  return (
    <EditorContext.Provider value={{ editor }}>
      <div className="controls-bar">
        <div className="control-item">
          <ButtonGroup orientation="horizontal">
            <AiAskButton />
          </ButtonGroup>
        </div>
      </div>

      <EditorContent editor={editor} role="presentation" className="control-showcase">
        <AiMenu />
      </EditorContent>
    </EditorContext.Provider>
  )
}

Props

NameTypeDefaultDescription
editorEditor | nullundefinedThe Tiptap editor instance

<AiMenuStateProvider />

State provider that manages AI menu state across the application.

Usage

import { AiMenuStateProvider } from '@/components/tiptap-ui/ai-menu'

function App() {
  return <AiMenuStateProvider>{/* Your editor components */}</AiMenuStateProvider>
}

<AiMenuContent />

The main content component that renders the AI menu interface.

Props

NameTypeDefaultDescription
editorEditor | nullundefinedThe Tiptap editor instance

Hooks

useAiMenuState()

Hook to access and manage AI menu state.

Usage

import { useAiMenuState } from '@/components/tiptap-ui/ai-menu'

function MyComponent() {
  const { state, updateState, setFallbackAnchor, reset } = useAiMenuState()

  const handleOpenMenu = () => {
    updateState({ isOpen: true, shouldShowInput: true })
  }

  const handleCloseMenu = () => {
    reset()
  }

  return (
    <button onClick={state.isOpen ? handleCloseMenu : handleOpenMenu}>
      {state.isOpen ? 'Close AI Menu' : 'Open AI Menu'}
    </button>
  )
}

Return Values

NameTypeDescription
stateAiMenuStateCurrent AI menu state
updateState(updates: Partial<AiMenuState>) => voidFunction to update menu state
setFallbackAnchor(element: HTMLElement | null, rect?) => voidSet fallback positioning anchor
reset() => voidReset menu state to initial state

useAiContentTracker()

Hook to track AI-generated content changes in the editor.

Usage

import { useAiContentTracker } from '@/components/tiptap-ui/ai-menu'

function MyComponent() {
  const { editor } = useTiptapEditor()

  useAiContentTracker(editor, {
    onContentChange: (hasAiContent) => {
      console.log('AI content detected:', hasAiContent)
    },
  })

  return <div>Component with AI content tracking</div>
}

useTextSelectionTracker()

Hook to track text selection changes for menu positioning.

Usage

import { useTextSelectionTracker } from '@/components/tiptap-ui/ai-menu'

function MyComponent() {
  const { editor } = useTiptapEditor()

  useTextSelectionTracker(editor, {
    onSelectionChange: (selection) => {
      console.log('Selection changed:', selection)
    },
  })

  return <div>Component with selection tracking</div>
}

Sub-Components

<AiMenuItems />

Component that renders the available AI actions grouped by category.

Usage

import { AiMenuItems } from '@/components/tiptap-ui/ai-menu'

function CustomAiMenu() {
  const { editor } = useTiptapEditor()

  return (
    <AiMenuItems
      editor={editor}
      availableActions={['improveWriting', 'aiFixSpellingAndGrammar', 'summarize']}
    />
  )
}

<AiMenuActions />

Component that renders action buttons for the AI menu (Accept, Regenerate, etc.).

Usage

import { AiMenuActions } from '@/components/tiptap-ui/ai-menu'

function CustomAiMenu() {
  const { editor } = useTiptapEditor()

  return (
    <AiMenuActions
      editor={editor}
      onAccept={() => console.log('AI content accepted')}
      onRegenerate={() => console.log('Regenerating AI content')}
    />
  )
}

<AiMenuInputTextarea />

Input component for custom AI prompts.

Usage

import { AiMenuInputTextarea } from '@/components/tiptap-ui/ai-menu'

function CustomPromptInput() {
  const handleSubmit = (prompt: string) => {
    console.log('User prompt:', prompt)
  }

  return (
    <AiMenuInputTextarea
      onSubmit={handleSubmit}
      placeholder="Ask AI to help with your content..."
    />
  )
}

Utilities

getContextAndInsertAt(editor)

Utility function to determine the context and insertion point for AI operations.

import { getContextAndInsertAt } from '@/components/tiptap-ui/ai-menu'

const { context, insertAt, isSelection } = getContextAndInsertAt(editor)

if (isSelection) {
  // Handle selection-based AI operation
  editor.chain().aiEdit({ prompt: 'Improve this text', insertAt }).run()
} else {
  // Handle insertion-based AI operation
  editor.chain().aiGenerate({ prompt: 'Write about AI', insertAt }).run()
}

findPrioritizedAIElement(editor)

Finds the most appropriate DOM element for AI menu positioning.

import { findPrioritizedAIElement } from '@/components/tiptap-ui/ai-menu'

const targetElement = findPrioritizedAIElement(editor)
if (targetElement) {
  // Position menu relative to this element
}

AI Actions

The AI Menu provides several built-in actions organized by category:

Edit Actions

  • Adjust Tone: Change the tone of selected text
  • Fix Spelling & Grammar: Correct spelling and grammar errors
  • Extend: Expand on the selected content
  • Shorten: Make the content more concise
  • Simplify Language: Use simpler, clearer language
  • Improve Writing: Enhance overall writing quality
  • Emojify: Add relevant emojis to the content

Write Actions

  • Continue Writing: Generate continuation of the content
  • Summarize: Create a summary of the selected text
  • Translate To: Translate content to different languages

State Management

AiMenuState Interface

interface AiMenuState {
  isOpen: boolean
  tone?: string
  language: string
  shouldShowInput: boolean
  inputIsFocused: boolean
  fallbackAnchor: {
    element: HTMLElement | null
    rect: DOMRect | null
  }
}

State Updates

The AI menu state can be updated using the updateState function:

const { updateState } = useAiMenuState()

// Open menu with input focused
updateState({
  isOpen: true,
  shouldShowInput: true,
  inputIsFocused: true,
})

// Set language for translation
updateState({ language: 'es' })

// Set tone for content adjustment
updateState({ tone: 'professional' })

Requirements

Dependencies

  • @tiptap/react - Core Tiptap React integration
  • @tiptap-pro/extension-ai - AI extension for content generation
  • @tiptap/starter-kit - Basic Tiptap extensions
  • react-hotkeys-hook - Keyboard shortcut management

Extensions

  • ui-state-extension - Manages UI state for AI operations
  • selection-extension - Enhanced text selection handling

Referenced Components

  • use-tiptap-editor (hook)
  • use-ui-editor-state (hook)
  • menu (primitive)
  • button, button-group (primitive)
  • card (primitive)
  • combobox (primitive)
  • tiptap-utils (lib)
  • sparkles-icon, stop-circle-2-icon (icons)

Configuration

AI Provider Setup

import { AiProvider } from '@/contexts/ai-context'

function App() {
  return (
    <AiProvider appId="your-app-id" token="your-ai-token">
      <YourEditor />
    </AiProvider>
  )
}