> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/ueberdosis/tiptap/llms.txt
> Use this file to discover all available pages before exploring further.

# Vue 3

> Learn how to integrate Tiptap with Vue 3 using the Composition API

Tiptap provides native Vue 3 support with composables and components that work seamlessly with the Composition API.

## Installation

<Steps>
  <Step title="Install the packages">
    Install the Vue 3 package along with the core package and any extensions you need:

    ```bash theme={null}
    npm install @tiptap/vue-3 @tiptap/core @tiptap/starter-kit
    ```
  </Step>

  <Step title="Import and use">
    Import the necessary components and composables in your Vue component:

    ```vue theme={null}
    <script setup>
    import { useEditor, EditorContent } from '@tiptap/vue-3'
    import StarterKit from '@tiptap/starter-kit'
    </script>
    ```
  </Step>
</Steps>

## Core Concepts

### useEditor Composable

The `useEditor` composable is the primary way to create and manage an editor instance in Vue 3. It returns a shallow ref containing the editor instance.

#### Type Signature

```typescript theme={null}
function useEditor(
  options?: Partial<EditorOptions>
): Ref<Editor | undefined>
```

#### Basic Usage with Composition API

```vue theme={null}
<template>
  <editor-content :editor="editor" />
</template>

<script setup>
import { useEditor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'

const editor = useEditor({
  extensions: [StarterKit],
  content: '<p>Hello World!</p>',
})
</script>
```

#### Options API Usage

You can also use the `Editor` class directly with the Options API:

```vue theme={null}
<template>
  <editor-content v-if="editor" :editor="editor" />
</template>

<script>
import { Editor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'

export default {
  components: {
    EditorContent,
  },

  data() {
    return {
      editor: null,
    }
  },

  mounted() {
    this.editor = new Editor({
      extensions: [StarterKit],
      content: '<p>Hello World!</p>',
    })
  },

  beforeUnmount() {
    this.editor.destroy()
  },
}
</script>
```

### EditorContent Component

The `EditorContent` component renders the actual editor interface.

```typescript theme={null}
interface EditorContentProps {
  editor: Editor | null
}
```

#### Usage Example

```vue theme={null}
<template>
  <div class="editor-wrapper">
    <editor-content 
      :editor="editor" 
      class="prose max-w-none"
    />
  </div>
</template>

<script setup>
import { useEditor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'

const editor = useEditor({
  extensions: [StarterKit],
  content: '<p>Hello World!</p>',
})
</script>

<style scoped>
.editor-wrapper {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 1rem;
}
</style>
```

## Complete Working Examples

<CodeGroup>
  ```vue Composition API theme={null}
  <template>
    <div v-if="editor" class="container">
      <div class="menu-bar">
        <button
          @click="editor.chain().focus().toggleBold().run()"
          :disabled="!editor.can().chain().focus().toggleBold().run()"
          :class="{ 'is-active': editor.isActive('bold') }"
        >
          Bold
        </button>
        <button
          @click="editor.chain().focus().toggleItalic().run()"
          :disabled="!editor.can().chain().focus().toggleItalic().run()"
          :class="{ 'is-active': editor.isActive('italic') }"
        >
          Italic
        </button>
        <button
          @click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
          :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
        >
          H1
        </button>
        <button
          @click="editor.chain().focus().toggleBulletList().run()"
          :class="{ 'is-active': editor.isActive('bulletList') }"
        >
          Bullet List
        </button>
      </div>
      
      <editor-content :editor="editor" />
    </div>
  </template>

  <script setup>
  import { useEditor, EditorContent } from '@tiptap/vue-3'
  import StarterKit from '@tiptap/starter-kit'

  const editor = useEditor({
    extensions: [StarterKit],
    content: `
      <h2>Hi there,</h2>
      <p>this is a <em>basic</em> example of <strong>Tiptap</strong>.</p>
    `,
  })
  </script>

  <style scoped>
  .menu-bar {
    display: flex;
    gap: 0.5rem;
    margin-bottom: 1rem;
    padding: 0.5rem;
    border: 1px solid #ddd;
    border-radius: 4px;
  }

  button {
    padding: 0.5rem 1rem;
    border: 1px solid #ddd;
    border-radius: 4px;
    background: white;
    cursor: pointer;
  }

  button:hover {
    background: #f5f5f5;
  }

  button.is-active {
    background: #333;
    color: white;
  }

  button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
  </style>
  ```

  ```vue Options API theme={null}
  <template>
    <div v-if="editor" class="container">
      <div class="control-group">
        <div class="button-group">
          <button
            @click="editor.chain().focus().toggleBold().run()"
            :disabled="!editor.can().chain().focus().toggleBold().run()"
            :class="{ 'is-active': editor.isActive('bold') }"
          >
            Bold
          </button>
          <button
            @click="editor.chain().focus().toggleItalic().run()"
            :disabled="!editor.can().chain().focus().toggleItalic().run()"
            :class="{ 'is-active': editor.isActive('italic') }"
          >
            Italic
          </button>
          <button
            @click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
            :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
          >
            H1
          </button>
          <button
            @click="editor.chain().focus().toggleBulletList().run()"
            :class="{ 'is-active': editor.isActive('bulletList') }"
          >
            Bullet list
          </button>
        </div>
      </div>
      <editor-content :editor="editor" />
    </div>
  </template>

  <script>
  import { ListItem } from '@tiptap/extension-list'
  import { Color, TextStyle } from '@tiptap/extension-text-style'
  import StarterKit from '@tiptap/starter-kit'
  import { Editor, EditorContent } from '@tiptap/vue-3'

  export default {
    components: {
      EditorContent,
    },

    data() {
      return {
        editor: null,
      }
    },

    mounted() {
      this.editor = new Editor({
        extensions: [
          Color.configure({ types: [TextStyle.name, ListItem.name] }),
          TextStyle.configure({ types: [ListItem.name] }),
          StarterKit,
        ],
        content: `
          <h2>Hi there,</h2>
          <p>
            this is a <em>basic</em> example of <strong>Tiptap</strong>.
          </p>
        `,
      })
    },

    beforeUnmount() {
      this.editor.destroy()
    },
  }
  </script>
  ```

  ```vue Minimal theme={null}
  <template>
    <editor-content :editor="editor" />
  </template>

  <script setup>
  import Document from '@tiptap/extension-document'
  import Paragraph from '@tiptap/extension-paragraph'
  import Text from '@tiptap/extension-text'
  import { useEditor, EditorContent } from '@tiptap/vue-3'

  const editor = useEditor({
    extensions: [Document, Paragraph, Text],
    content: '<p>This is a minimal Tiptap editor.</p>',
  })
  </script>
  ```
</CodeGroup>

## Event Handlers

Handle editor events through the editor options:

```vue theme={null}
<script setup>
import { useEditor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import { ref } from 'vue'

const html = ref('')

const editor = useEditor({
  extensions: [StarterKit],
  content: '<p>Hello World!</p>',
  
  onUpdate: ({ editor }) => {
    html.value = editor.getHTML()
  },
  
  onCreate: ({ editor }) => {
    console.log('Editor created')
  },
  
  onFocus: ({ editor, event }) => {
    console.log('Editor focused')
  },
  
  onBlur: ({ editor, event }) => {
    console.log('Editor blurred')
  },
})
</script>

<template>
  <div>
    <editor-content :editor="editor" />
    <div>HTML Output: {{ html }}</div>
  </div>
</template>
```

## Reactivity

The editor instance is a shallow ref, so you need to use `.value` when accessing it in script:

```vue theme={null}
<script setup>
import { useEditor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import { watch } from 'vue'

const editor = useEditor({
  extensions: [StarterKit],
  content: '<p>Hello World!</p>',
})

// Watch editor changes
watch(editor, (newEditor) => {
  if (newEditor) {
    console.log('Editor is ready')
  }
})

// Access editor in functions
function getContent() {
  return editor.value?.getHTML()
}
</script>
```

## Advanced: Custom Node Views

Create Vue components as custom node views:

```vue theme={null}
<!-- CustomNodeComponent.vue -->
<template>
  <node-view-wrapper class="custom-node">
    <div class="label">Custom Vue Node</div>
    <node-view-content class="content" />
  </node-view-wrapper>
</template>

<script setup>
import { NodeViewWrapper, NodeViewContent, nodeViewProps } from '@tiptap/vue-3'

defineProps(nodeViewProps)
</script>

<style scoped>
.custom-node {
  border: 2px solid #333;
  border-radius: 8px;
  padding: 1rem;
}

.label {
  font-weight: bold;
  margin-bottom: 0.5rem;
}
</style>
```

```typescript theme={null}
// CustomNodeExtension.ts
import { Node } from '@tiptap/core'
import { VueNodeViewRenderer } from '@tiptap/vue-3'
import CustomNodeComponent from './CustomNodeComponent.vue'

export const CustomNode = Node.create({
  name: 'customNode',
  
  group: 'block',
  
  content: 'inline*',
  
  parseHTML() {
    return [{ tag: 'div[data-type="custom-node"]' }]
  },
  
  renderHTML({ HTMLAttributes }) {
    return ['div', { 'data-type': 'custom-node', ...HTMLAttributes }, 0]
  },
  
  addNodeView() {
    return VueNodeViewRenderer(CustomNodeComponent)
  },
})
```

## Menus

Bubble and floating menus work seamlessly with Vue 3:

```vue theme={null}
<template>
  <div>
    <editor-content :editor="editor" />
    
    <bubble-menu :editor="editor" v-if="editor">
      <button @click="editor.chain().focus().toggleBold().run()">
        Bold
      </button>
      <button @click="editor.chain().focus().toggleItalic().run()">
        Italic
      </button>
    </bubble-menu>
    
    <floating-menu :editor="editor" v-if="editor">
      <button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()">
        H1
      </button>
      <button @click="editor.chain().focus().toggleBulletList().run()">
        Bullet List
      </button>
    </floating-menu>
  </div>
</template>

<script setup>
import { BubbleMenu, FloatingMenu } from '@tiptap/vue-3'
import { useEditor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'

const editor = useEditor({
  extensions: [StarterKit],
  content: '<p>Hello World!</p>',
})
</script>
```

## TypeScript Support

Tiptap has full TypeScript support:

```vue theme={null}
<script setup lang="ts">
import type { Editor } from '@tiptap/core'
import type { Ref } from 'vue'
import { useEditor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'

const editor: Ref<Editor | undefined> = useEditor({
  extensions: [StarterKit],
  content: '<p>Hello World!</p>',
})
</script>
```

## Provide/Inject Pattern

Share the editor instance across components using Vue's provide/inject:

```vue theme={null}
<!-- Parent.vue -->
<template>
  <div>
    <Toolbar />
    <editor-content :editor="editor" />
  </div>
</template>

<script setup>
import { provide } from 'vue'
import { useEditor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import Toolbar from './Toolbar.vue'

const editor = useEditor({
  extensions: [StarterKit],
  content: '<p>Hello World!</p>',
})

provide('editor', editor)
</script>
```

```vue theme={null}
<!-- Toolbar.vue -->
<template>
  <div v-if="editor" class="toolbar">
    <button @click="editor.chain().focus().toggleBold().run()">
      Bold
    </button>
  </div>
</template>

<script setup>
import { inject } from 'vue'

const editor = inject('editor')
</script>
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Extensions" icon="puzzle-piece" href="/extensions">
    Explore available extensions to enhance your editor
  </Card>

  <Card title="Commands" icon="terminal" href="/api/commands">
    Learn about editor commands and chains
  </Card>

  <Card title="Styling" icon="paintbrush" href="/guide/styling">
    Customize the editor's appearance
  </Card>

  <Card title="Node Views" icon="cube" href="/guide/node-views">
    Create interactive custom nodes with Vue components
  </Card>
</CardGroup>
