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.
Tiptap provides native Vue 3 support with composables and components that work seamlessly with the Composition API.
Installation
Install the packages
Install the Vue 3 package along with the core package and any extensions you need:npm install @tiptap/vue-3 @tiptap/core @tiptap/starter-kit
Import and use
Import the necessary components and composables in your Vue component:<script setup>
import { useEditor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
</script>
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
function useEditor(
options?: Partial<EditorOptions>
): Ref<Editor | undefined>
Basic Usage with Composition API
<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:
<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.
interface EditorContentProps {
editor: Editor | null
}
Usage Example
<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
<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>
Event Handlers
Handle editor events through the editor options:
<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:
<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:
<!-- 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>
// 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)
},
})
Bubble and floating menus work seamlessly with Vue 3:
<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:
<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:
<!-- 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>
<!-- 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
Extensions
Explore available extensions to enhance your editor
Commands
Learn about editor commands and chains
Styling
Customize the editor’s appearance
Node Views
Create interactive custom nodes with Vue components