Skip to main content
ProseKit provides first-class support for Svelte 5 through the @prosekit/svelte package, offering Svelte-specific components, runes, and utilities for building powerful editors.

Installation

npm install prosekit @prosekit/svelte
Peer dependencies:
  • svelte >= 5.0.0

Quick Start

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

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

<ProseKit {editor}>
  <EditorContent />
</ProseKit>
<!-- EditorContent.svelte -->
<script lang="ts">
import { useEditor } from '@prosekit/svelte'
import { onMount } from 'svelte'

const editor = useEditor()

let editorElement: HTMLElement

onMount(() => {
  if (editorElement) {
    $editor.mount(editorElement)
  }
})
</script>

<div bind:this={editorElement}></div>

Core Components

ProseKit

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

let editor: Editor
</script>

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

Essential Functions

useEditor

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

const editor = useEditor()

function toggleBold() {
  $editor.commands.toggleBold()
}
</script>

<button onclick={toggleBold}>Bold</button>
Returns: Readable<Editor> (Svelte store) Options:
  • update?: boolean - Whether to update the store when editor state changes (default: false)
// Update store on every state update
const editor = useEditor({ update: true })

useEditorDerivedValue

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

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

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

useExtension

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

let enableCodeBlock = $state(false)

const extension = $derived(enableCodeBlock ? defineCodeBlock() : null)

useExtension(extension)
</script>

<label>
  <input type="checkbox" bind:checked={enableCodeBlock} />
  Enable Code Blocks
</label>
Options:
  • editor?: Editor - Optional editor instance (defaults to context editor)
  • priority?: Priority - Extension priority

useKeymap

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

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

useDocChange

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

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 lang="ts">
import { useStateUpdate } from '@prosekit/svelte'

let selection = $state({ from: 0, to: 0 })

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

Custom Node Views

Create interactive node views with Svelte components:
// image-extension.ts
import { defineSvelteNodeView } from '@prosekit/svelte'
import ImageComponent from './ImageComponent.svelte'

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

let { node, setAttrs }: SvelteNodeViewProps = $props()

function handleClick() {
  setAttrs({ selected: true })
}
</script>

<img 
  src={node.attrs.src} 
  alt={node.attrs.alt}
  onclick={handleClick}
/>
SvelteNodeViewProps:
  • 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 Svelte:
// comment-extension.ts
import { defineSvelteMarkView } from '@prosekit/svelte'
import CommentMark from './CommentMark.svelte'

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

let { mark, children }: SvelteMarkViewProps = $props()
const commentId = mark.attrs.id
</script>

<span 
  class="comment"
  data-comment-id={commentId}
>
  {@render children()}
</span>

UI Components

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

Autocomplete

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

<AutocompletePopover>
  <AutocompleteList>
    <AutocompleteEmpty>No results</AutocompleteEmpty>
    <AutocompleteItem value="svelte">Svelte</AutocompleteItem>
    <AutocompleteItem value="react">React</AutocompleteItem>
  </AutocompleteList>
</AutocompletePopover>

Inline Popover

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

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

Tooltip

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

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

Popover

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

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

Block Handle

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

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

Table Handle

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

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

Resizable

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

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

TypeScript Support

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

const extension = defineBasicExtension()

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

const editorStore = useEditor()
// $editorStore.commands has autocomplete for all available commands
$editorStore.commands.toggleBold()

Svelte 5 Runes

ProseKit works seamlessly with Svelte 5’s new runes:
<script lang="ts">
import { createEditor } from '@prosekit/core'
import { ProseKit, useEditor } from '@prosekit/svelte'
import { defineBasicExtension } from '@prosekit/extensions'

let content = $state('')

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

const editorRef = useEditor()

$effect(() => {
  content = $editorRef.state.doc.textContent
})
</script>

<ProseKit {editor}>
  <div>Content length: {content.length}</div>
</ProseKit>

Server-Side Rendering (SSR)

ProseKit supports server-side rendering with SvelteKit:
<script lang="ts">
import { createEditor } from '@prosekit/core'
import { ProseKit } from '@prosekit/svelte'
import { defineBasicExtension } from '@prosekit/extensions'
import { onMount } from 'svelte'
import { browser } from '$app/environment'

let editor = $state(null)

onMount(() => {
  if (browser) {
    editor = createEditor({ 
      extension: defineBasicExtension() 
    })
  }
})
</script>

{#if editor}
  <ProseKit {editor}>
    <!-- Editor components -->
  </ProseKit>
{/if}

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