---
title: "AI Menu"
description: "Intelligent AI-powered contextual menu for content editing and generation. More in the docs."
canonical_url: "https://tiptap.dev/docs/ui-components/components/ai-menu"
---

# AI Menu

Intelligent AI-powered contextual menu for content editing and generation. More in the docs.

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.

> **Interactive demo:** [ai menu](https://template.tiptap.dev/preview/tiptap-ui/ai-menu)

## Installation

Add the component via the Tiptap CLI:

```bash
npx @tiptap/cli@latest add ai-menu
```

## Components

### `<AiMenu />`

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

#### Usage

```tsx
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 anchorToSelection />
      </EditorContent>
    </EditorContext.Provider>
  )
}
```

#### Props

| Name                | Type             | Default     | Description                                                                                                               |
| ------------------- | ---------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------- |
| `editor`            | `Editor \| null` | `undefined` | The Tiptap editor instance                                                                                                |
| `anchorToSelection` | `boolean`        | `false`     | When `true`, anchors the menu to the current text selection spanning the full editor width, with dynamic scroll tracking. |

### `<AiMenuStateProvider />`

State provider that manages AI menu state across the application.

#### Usage

```tsx
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

| Name                | Type             | Default     | Description                                                                                                               |
| ------------------- | ---------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------- |
| `editor`            | `Editor \| null` | `undefined` | The Tiptap editor instance                                                                                                |
| `anchorToSelection` | `boolean`        | `false`     | When `true`, anchors the menu to the current text selection spanning the full editor width, with dynamic scroll tracking. |

## Hooks

### `useAiMenuState()`

Hook to access and manage AI menu state.

#### Usage

```tsx
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

| Name                | Type                                            | Description                       |
| ------------------- | ----------------------------------------------- | --------------------------------- |
| `state`             | `AiMenuState`                                   | Current AI menu state             |
| `updateState`       | `(updates: Partial<AiMenuState>) => void`       | Function to update menu state     |
| `setFallbackAnchor` | `(element: HTMLElement \| null, rect?) => void` | Set fallback positioning anchor   |
| `reset`             | `() => void`                                    | Reset menu state to initial state |

### `useAiContentTracker()`

Hook to track AI-generated content changes in the editor. Supports two positioning modes: a **streaming mode** (when `anchorToSelection` is `true` and AI is loading) that uses a `ResizeObserver` to continuously reposition the anchor as content streams in, and a **static mode** that creates a scroll-aware virtual anchor at the AI element's position.

#### Usage

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

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

  useAiContentTracker({
    editor,
    aiGenerationActive: true,
    setAnchorElement: (el) => store.setAnchorElement(el),
    anchorToSelection: true,
  })

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

#### Parameters

| Name                 | Type                             | Default | Description                                                                 |
| -------------------- | -------------------------------- | ------- | --------------------------------------------------------------------------- |
| `editor`             | `Editor \| null`                 | —       | The Tiptap editor instance                                                  |
| `aiGenerationActive` | `boolean`                        | —       | Whether AI generation is currently active                                   |
| `setAnchorElement`   | `(element: HTMLElement) => void` | —       | Callback to set the floating menu's anchor element                          |
| `anchorToSelection`  | `boolean`                        | `false` | Use editor-width virtual anchors with scroll tracking and streaming support |

### `useTextSelectionTracker()`

Hook to track text selection changes for menu positioning. When `anchorToSelection` is `true`, creates editor-width virtual anchors at the selection's vertical position instead of using the raw selected DOM element.

#### Usage

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

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

  useTextSelectionTracker({
    editor,
    aiGenerationActive: true,
    showMenuAtElement: (el) => show(el),
    setMenuVisible: (visible) => updateState({ isOpen: visible }),
    anchorToSelection: true,
  })

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

#### Parameters

| Name                 | Type                                                            | Default | Description                                                           |
| -------------------- | --------------------------------------------------------------- | ------- | --------------------------------------------------------------------- |
| `editor`             | `Editor \| null`                                                | —       | The Tiptap editor instance                                            |
| `aiGenerationActive` | `boolean`                                                       | —       | Whether AI generation is currently active                             |
| `showMenuAtElement`  | `(element: HTMLElement) => void`                                | —       | Callback to show the menu at a given element                          |
| `setMenuVisible`     | `(visible: boolean) => void`                                    | —       | Callback to toggle menu visibility                                    |
| `onSelectionChange`  | `(element: HTMLElement \| null, rect: DOMRect \| null) => void` | —       | Optional callback when selection changes                              |
| `prevent`            | `boolean`                                                       | `false` | Prevents tracking when `true`                                         |
| `anchorToSelection`  | `boolean`                                                       | `false` | Use editor-width virtual anchors at the selection's vertical position |

## Sub-Components

### `<AiMenuItems />`

Component that renders the available AI actions grouped by category.

#### Usage

```tsx
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

```tsx
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

```tsx
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.

```tsx
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.

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

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

### `getSelectionRangeRect(editor)`

Gets the bounding rect of the current text selection using the browser's Selection API, with a ProseMirror coordinate fallback. Returns `null` if the selection is empty.

```tsx
import { getSelectionRangeRect } from '@/components/tiptap-ui/ai-menu'

const rect = getSelectionRangeRect(editor)
if (rect) {
  // Use rect for positioning
}
```

### `createEditorWidthAnchorRect(editorDom, sourceRect)`

Creates a full-width anchor `DOMRect` spanning the editor's content area (excluding padding and border) at the vertical position of the given source rect.

```tsx
import { createEditorWidthAnchorRect } from '@/components/tiptap-ui/ai-menu'

const selectionRect = getSelectionRangeRect(editor)
if (selectionRect) {
  const anchorRect = createEditorWidthAnchorRect(editor.view.dom, selectionRect)
  // anchorRect spans the full editor content width at the selection's vertical position
}
```

### `createVirtualAnchor(rect, referenceElement?)`

Creates a virtual anchor element that reports a bounding rect via `getBoundingClientRect()`. When a `referenceElement` is provided, the anchor stores offsets relative to the reference and dynamically recomputes viewport coordinates on each call, enabling scroll-aware positioning.

```tsx
import { createVirtualAnchor, createEditorWidthAnchorRect } from '@/components/tiptap-ui/ai-menu'

const anchorRect = createEditorWidthAnchorRect(editor.view.dom, selectionRect)
const anchor = createVirtualAnchor(anchorRect, editor.view.dom)

// The anchor's getBoundingClientRect() will update as the editor scrolls
store.setAnchorElement(anchor)
```

### `getEditorContentRect(editorDom)`

Computes the content-area rect of the editor element, excluding padding and border. Returns an object with `left` and `width` properties.

```tsx
import { getEditorContentRect } from '@/components/tiptap-ui/ai-menu'

const { left, width } = getEditorContentRect(editor.view.dom)
```

## 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

```tsx
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:

```tsx
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

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

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