Skip to main content
ProseKit provides first-class support for Vue 3 through the @prosekit/vue package, offering Vue-specific components, composables, and utilities for building powerful editors.

Installation

npm install prosekit @prosekit/vue
Peer dependencies:
  • vue >= 3.0.0

Quick Start

Create a basic editor with Vue:
<script setup lang="ts">
import { createEditor } from '@prosekit/core'
import { ProseKit, useEditor } from '@prosekit/vue'
import { defineBasicExtension } from '@prosekit/extensions'

const editor = createEditor({
  extension: defineBasicExtension()
})
</script>

<template>
  <ProseKit :editor="editor">
    <EditorContent />
  </ProseKit>
</template>
<!-- EditorContent.vue -->
<script setup lang="ts">
import { useEditor } from '@prosekit/vue'
import { onMounted, ref } from 'vue'

const editor = useEditor()
const editorRef = ref<HTMLElement>()

onMounted(() => {
  if (editorRef.value) {
    editor.value.mount(editorRef.value)
  }
})
</script>

<template>
  <div ref="editorRef" />
</template>

Core Components

ProseKit

The root component that provides editor context to all child components.
<script setup lang="ts">
import { ProseKit } from '@prosekit/vue'
import type { ProseKitProps } from '@prosekit/vue'

const props = defineProps<ProseKitProps>()
</script>

<template>
  <ProseKit :editor="editor">
    <!-- Your editor UI components -->
  </ProseKit>
</template>
Props:
  • editor: The editor instance created with createEditor()

Essential Composables

useEditor

Retrieves the editor instance from the nearest ProseKit component.
<script setup lang="ts">
import { useEditor } from '@prosekit/vue'

const editor = useEditor()

const toggleBold = () => {
  editor.value.commands.toggleBold()
}
</script>

<template>
  <button @click="toggleBold">Bold</button>
</template>
Returns: ShallowRef<Editor> Options:
  • update?: boolean - Whether to trigger reactivity when editor state updates (default: false)
// Trigger reactivity on every state update
const editor = useEditor({ update: true })

useEditorDerivedValue

Compute and subscribe to derived values from the editor state.
<script setup lang="ts">
import { useEditorDerivedValue } from '@prosekit/vue'
import { computed } from 'vue'

const wordCount = useEditorDerivedValue((editor) => {
  return editor.state.doc.textContent.split(/\s+/).length
})
</script>

<template>
  <div>Words: {{ wordCount }}</div>
</template>

useExtension

Dynamically add extensions to the editor.
<script setup lang="ts">
import { useExtension } from '@prosekit/vue'
import { defineCodeBlock } from '@prosekit/extensions'
import { computed } from 'vue'

const props = defineProps<{ enableCodeBlock: boolean }>()

const extension = computed(() => 
  props.enableCodeBlock ? defineCodeBlock() : null
)

useExtension(extension)
</script>
Options:
  • editor?: Editor - Optional editor instance (defaults to context editor)
  • priority?: Priority - Extension priority

useKeymap

Define keyboard shortcuts.
<script setup lang="ts">
import { useKeymap } from '@prosekit/vue'

useKeymap({
  'Mod-s': () => {
    console.log('Save triggered')
    return true
  }
})
</script>

useDocChange

Execute a callback when the document changes.
<script setup lang="ts">
import { useDocChange } from '@prosekit/vue'

useDocChange((editor) => {
  const json = editor.getDocJSON()
  localStorage.setItem('doc', JSON.stringify(json))
})
</script>

useStateUpdate

Execute a callback on every state update (including selection changes).
<script setup lang="ts">
import { useStateUpdate } from '@prosekit/vue'
import { ref } from 'vue'

const selection = ref({ from: 0, to: 0 })

useStateUpdate((editor) => {
  selection.value = editor.state.selection
})
</script>

Custom Node Views

Create interactive node views with Vue components:
// image-extension.ts
import { defineVueNodeView } from '@prosekit/vue'
import ImageComponent from './ImageComponent.vue'

export const imageExtension = defineVueNodeView({
  name: 'image',
  component: ImageComponent,
  contentDOM: false,
})
<!-- ImageComponent.vue -->
<script setup lang="ts">
import type { VueNodeViewProps } from '@prosekit/vue'

const props = defineProps<VueNodeViewProps>()

const handleClick = () => {
  props.setAttrs({ selected: true })
}
</script>

<template>
  <img 
    :src="props.node.attrs.src" 
    :alt="props.node.attrs.alt"
    @click="handleClick"
  />
</template>
VueNodeViewProps:
  • node - The ProseMirror node
  • view - The editor view
  • getPos - Function to get node position
  • setAttrs - Function to update node attributes
  • decorations - Node decorations
  • selected - Whether the node is selected

Custom Mark Views

Create custom mark rendering with Vue:
// comment-extension.ts
import { defineVueMarkView } from '@prosekit/vue'
import CommentMark from './CommentMark.vue'

export const commentExtension = defineVueMarkView({
  name: 'comment',
  component: CommentMark,
})
<!-- CommentMark.vue -->
<script setup lang="ts">
import type { VueMarkViewProps } from '@prosekit/vue'

const props = defineProps<VueMarkViewProps>()
const commentId = props.mark.attrs.id
</script>

<template>
  <span 
    class="comment"
    :data-comment-id="commentId"
  >
    <slot />
  </span>
</template>

UI Components

ProseKit provides pre-built Vue components for common editor UI patterns:

Autocomplete

<script setup lang="ts">
import { 
  AutocompletePopover, 
  AutocompleteItem, 
  AutocompleteList, 
  AutocompleteEmpty 
} from '@prosekit/vue/autocomplete'
</script>

<template>
  <AutocompletePopover>
    <AutocompleteList>
      <AutocompleteEmpty>No results</AutocompleteEmpty>
      <AutocompleteItem value="vue">Vue</AutocompleteItem>
      <AutocompleteItem value="react">React</AutocompleteItem>
    </AutocompleteList>
  </AutocompletePopover>
</template>

Inline Popover

<script setup lang="ts">
import { InlinePopover } from '@prosekit/vue/inline-popover'
</script>

<template>
  <InlinePopover>
    <button>Bold</button>
    <button>Italic</button>
  </InlinePopover>
</template>

Tooltip

<script setup lang="ts">
import { TooltipRoot, TooltipTrigger, TooltipContent } from '@prosekit/vue/tooltip'
</script>

<template>
  <TooltipRoot>
    <TooltipTrigger>
      <button>Hover me</button>
    </TooltipTrigger>
    <TooltipContent>
      Tooltip text
    </TooltipContent>
  </TooltipRoot>
</template>

Popover

<script setup lang="ts">
import { PopoverRoot, PopoverTrigger, PopoverContent } from '@prosekit/vue/popover'
</script>

<template>
  <PopoverRoot>
    <PopoverTrigger>
      <button>Open</button>
    </PopoverTrigger>
    <PopoverContent>
      Popover content
    </PopoverContent>
  </PopoverRoot>
</template>

Block Handle

<script setup lang="ts">
import { BlockHandleDraggable, BlockHandleAdd, BlockHandlePopover } from '@prosekit/vue/block-handle'
</script>

<template>
  <BlockHandleDraggable />
  <BlockHandleAdd />
  <BlockHandlePopover>
    <!-- Block options -->
  </BlockHandlePopover>
</template>

Table Handle

<script setup lang="ts">
import { 
  TableHandleRoot,
  TableHandleColumnRoot,
  TableHandleColumnTrigger,
  TableHandleRowRoot,
  TableHandleRowTrigger 
} from '@prosekit/vue/table-handle'
</script>

<template>
  <TableHandleRoot>
    <TableHandleColumnRoot>
      <TableHandleColumnTrigger />
    </TableHandleColumnRoot>
    <TableHandleRowRoot>
      <TableHandleRowTrigger />
    </TableHandleRowRoot>
  </TableHandleRoot>
</template>

Resizable

<script setup lang="ts">
import { ResizableRoot, ResizableHandle } from '@prosekit/vue/resizable'
</script>

<template>
  <ResizableRoot>
    <img src="image.jpg" />
    <ResizableHandle />
  </ResizableRoot>
</template>

TypeScript Support

ProseKit has full TypeScript support with type inference:
import { createEditor } from '@prosekit/core'
import { useEditor } from '@prosekit/vue'
import { defineBasicExtension } from '@prosekit/extensions'

const extension = defineBasicExtension()

const editor = createEditor({ extension })
// Editor type is automatically inferred

const editorRef = useEditor()
// editorRef.value.commands has autocomplete for all available commands
editorRef.value.commands.toggleBold()

Composition API

ProseKit fully embraces Vue 3’s Composition API:
<script setup lang="ts">
import { createEditor } from '@prosekit/core'
import { ProseKit, useEditor } from '@prosekit/vue'
import { defineBasicExtension } from '@prosekit/extensions'
import { ref, onMounted } from 'vue'

const editor = createEditor({
  extension: defineBasicExtension()
})

const editorElement = ref<HTMLElement>()

onMounted(() => {
  if (editorElement.value) {
    editor.mount(editorElement.value)
  }
})
</script>

<template>
  <ProseKit :editor="editor">
    <div ref="editorElement" />
  </ProseKit>
</template>

Server-Side Rendering (SSR)

ProseKit supports server-side rendering with Nuxt and other Vue frameworks:
<script setup lang="ts">
import { createEditor } from '@prosekit/core'
import { ProseKit } from '@prosekit/vue'
import { defineBasicExtension } from '@prosekit/extensions'
import { shallowRef, onMounted } from 'vue'

const editor = shallowRef()

onMounted(() => {
  editor.value = createEditor({ 
    extension: defineBasicExtension() 
  })
})
</script>

<template>
  <ProseKit v-if="editor" :editor="editor">
    <!-- Editor components -->
  </ProseKit>
</template>

Examples

Check out full working examples:

Next Steps

Extensions

Learn about available extensions

Commands

Explore editor commands

Styling

Customize editor appearance

API Reference

Full API documentation