Tiptap provides support for Vue 2 applications. While Vue 2 has reached end-of-life, this integration allows legacy projects to use Tiptap.
Vue 2 reached end-of-life on December 31, 2023. Consider upgrading to Vue 3 for new projects. This package is maintained for existing Vue 2 applications.
Installation
Install the packages
Install the Vue 2 package along with the core package and any extensions you need: npm install @tiptap/vue-2 @tiptap/core @tiptap/starter-kit
Import and use
Import the necessary components in your Vue component: < script >
import { Editor , EditorContent } from '@tiptap/vue-2'
import StarterKit from '@tiptap/starter-kit'
</ script >
Core Concepts
Editor Class
In Vue 2, you create an editor instance using the Editor class directly. The editor should be created in the mounted lifecycle hook and destroyed in beforeDestroy.
Basic Usage
< template >
< div v-if = " editor " >
< editor-content : editor = " editor " />
</ div >
</ template >
< script >
import { Editor , EditorContent } from '@tiptap/vue-2'
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>' ,
})
} ,
beforeDestroy () {
this . editor . destroy ()
} ,
}
</ script >
EditorContent Component
The EditorContent component renders the actual editor interface.
< template >
< div class = "editor-wrapper" >
< editor-content
: editor = " editor "
class = "editor"
/>
</ div >
</ template >
< script >
import { Editor , EditorContent } from '@tiptap/vue-2'
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>' ,
})
} ,
beforeDestroy () {
this . editor . destroy ()
} ,
}
</ script >
< style scoped >
.editor-wrapper {
border : 1 px solid #ddd ;
border-radius : 8 px ;
padding : 1 rem ;
}
</ style >
Complete Working Examples
Basic Editor
With Toolbar
Minimal
< template >
< div v-if = " editor " class = "container" >
< editor-content : editor = " editor " />
</ div >
</ template >
< script >
import { Editor , EditorContent } from '@tiptap/vue-2'
import StarterKit from '@tiptap/starter-kit'
export default {
components: {
EditorContent ,
} ,
data () {
return {
editor: null ,
}
} ,
mounted () {
this . editor = new Editor ({
extensions: [ StarterKit ],
content: `
<h2>Hi there,</h2>
<p>this is a <em>basic</em> example of <strong>Tiptap</strong>.</p>
` ,
})
} ,
beforeDestroy () {
this . editor . destroy ()
} ,
}
</ script >
Event Handlers
Handle editor events through the editor options:
< template >
< div >
< editor-content : editor = " editor " />
< div > HTML Output: {{ html }} </ div >
</ div >
</ template >
< script >
import { Editor , EditorContent } from '@tiptap/vue-2'
import StarterKit from '@tiptap/starter-kit'
export default {
components: {
EditorContent ,
} ,
data () {
return {
editor: null ,
html: '' ,
}
} ,
mounted () {
const vm = this
this . editor = new Editor ({
extensions: [ StarterKit ],
content: '<p>Hello World!</p>' ,
onUpdate ({ editor }) {
vm . html = editor . getHTML ()
},
onCreate ({ editor }) {
console . log ( 'Editor created' )
},
onFocus ({ editor , event }) {
console . log ( 'Editor focused' )
},
onBlur ({ editor , event }) {
console . log ( 'Editor blurred' )
},
})
} ,
beforeDestroy () {
this . editor . destroy ()
} ,
}
</ script >
Methods and Computed Properties
You can create methods and computed properties to interact with the editor:
< template >
< div v-if = " editor " >
< div class = "stats" >
< span > Characters: {{ characterCount }} </ span >
< span > Words: {{ wordCount }} </ span >
</ div >
< button @ click = " clearContent " > Clear </ button >
< button @ click = " setContent " > Set Example Content </ button >
< editor-content : editor = " editor " />
</ div >
</ template >
< script >
import { Editor , EditorContent } from '@tiptap/vue-2'
import StarterKit from '@tiptap/starter-kit'
export default {
components: {
EditorContent ,
} ,
data () {
return {
editor: null ,
}
} ,
computed: {
characterCount () {
return this . editor ?. state . doc . textContent . length || 0
},
wordCount () {
const text = this . editor ?. state . doc . textContent || ''
return text . split ( / \s + / ). filter ( Boolean ). length
},
} ,
methods: {
clearContent () {
this . editor . commands . clearContent ()
},
setContent () {
this . editor . commands . setContent ( '<p>This is <strong>example</strong> content!</p>' )
},
} ,
mounted () {
this . editor = new Editor ({
extensions: [ StarterKit ],
content: '<p>Hello World!</p>' ,
})
} ,
beforeDestroy () {
this . editor . destroy ()
} ,
}
</ script >
Advanced: Custom Node Views
Create Vue 2 components as custom node views:
<!-- CustomNodeComponent.vue -->
< template >
< node-view-wrapper class = "custom-node" >
< div class = "label" > Custom Vue 2 Node </ div >
< node-view-content class = "content" />
</ node-view-wrapper >
</ template >
< script >
import { NodeViewWrapper , NodeViewContent } from '@tiptap/vue-2'
export default {
components: {
NodeViewWrapper ,
NodeViewContent ,
} ,
}
</ script >
< style scoped >
.custom-node {
border : 2 px solid #333 ;
border-radius : 8 px ;
padding : 1 rem ;
}
.label {
font-weight : bold ;
margin-bottom : 0.5 rem ;
}
</ style >
// CustomNodeExtension.js
import { Node } from '@tiptap/core'
import { VueNodeViewRenderer } from '@tiptap/vue-2'
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 )
},
})
Bubble and floating menus work with Vue 2:
< 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 >
import { BubbleMenu , FloatingMenu } from '@tiptap/vue-2'
import { Editor , EditorContent } from '@tiptap/vue-2'
import StarterKit from '@tiptap/starter-kit'
export default {
components: {
EditorContent ,
BubbleMenu ,
FloatingMenu ,
} ,
data () {
return {
editor: null ,
}
} ,
mounted () {
this . editor = new Editor ({
extensions: [ StarterKit ],
content: '<p>Hello World!</p>' ,
})
} ,
beforeDestroy () {
this . editor . destroy ()
} ,
}
</ script >
Watchers
You can use watchers to react to editor changes:
< script >
import { Editor , EditorContent } from '@tiptap/vue-2'
import StarterKit from '@tiptap/starter-kit'
export default {
components: {
EditorContent ,
} ,
data () {
return {
editor: null ,
content: '' ,
}
} ,
watch: {
content ( newContent ) {
// Update editor when external content changes
if ( this . editor && this . editor . getHTML () !== newContent ) {
this . editor . commands . setContent ( newContent )
}
},
} ,
mounted () {
const vm = this
this . editor = new Editor ({
extensions: [ StarterKit ],
content: this . content ,
onUpdate ({ editor }) {
vm . content = editor . getHTML ()
},
})
} ,
beforeDestroy () {
this . editor . destroy ()
} ,
}
</ script >
Migration from Vue 2 to Vue 3
If you’re upgrading from Vue 2 to Vue 3, here are the main changes:
Vue 2
Vue 3 (Options)
Vue 3 (Composition)
< script >
import { Editor , EditorContent } from '@tiptap/vue-2'
export default {
data () {
return { editor: null }
} ,
mounted () {
this . editor = new Editor ({ /* ... */ })
} ,
beforeDestroy () {
this . editor . destroy ()
} ,
}
</ script >
< script >
import { Editor , EditorContent } from '@tiptap/vue-3'
export default {
data () {
return { editor: null }
} ,
mounted () {
this . editor = new Editor ({ /* ... */ })
} ,
beforeUnmount () { // Changed from beforeDestroy
this . editor . destroy ()
} ,
}
</ script >
< script setup >
import { useEditor , EditorContent } from '@tiptap/vue-3'
const editor = useEditor ({ /* ... */ })
// Cleanup is handled automatically
</ script >
Next Steps
Extensions Explore available extensions to enhance your editor
Commands Learn about editor commands and chains
Upgrade to Vue 3 Learn about the Vue 3 integration
Node Views Create interactive custom nodes with Vue components