Skip to main content
ProseKit provides first-class support for Preact through the @prosekit/preact package, offering Preact-specific components, hooks, and utilities for building powerful editors with a smaller bundle size.

Installation

npm install prosekit @prosekit/preact
Peer dependencies:
  • preact >= 10.11.0

Quick Start

Create a basic editor with Preact:
import { createEditor } from '@prosekit/core'
import { ProseKit, useEditor } from '@prosekit/preact'
import { defineBasicExtension } from '@prosekit/extensions'
import { useEffect, useRef } from 'preact/hooks'

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

  return (
    <ProseKit editor={editor}>
      <EditorContent />
    </ProseKit>
  )
}

function EditorContent() {
  const editor = useEditor()
  const editorRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    if (editorRef.current) {
      editor.mount(editorRef.current)
    }
  }, [])

  return <div ref={editorRef} />
}

Core Components

ProseKit

The root component that provides editor context to all child components.
import { ProseKit } from '@prosekit/preact'
import type { ProseKitProps } from '@prosekit/preact'

<ProseKit editor={editor}>
  {/* Your editor UI components */}
</ProseKit>
Props:
  • editor: The editor instance created with createEditor()
  • children?: Child components

Essential Hooks

useEditor

Retrieves the editor instance from the nearest ProseKit component.
import { useEditor } from '@prosekit/preact'

function Toolbar() {
  const editor = useEditor()
  
  const toggleBold = () => {
    editor.commands.toggleBold()
  }
  
  return <button onClick={toggleBold}>Bold</button>
}
Options:
  • update?: boolean - Whether to re-render when editor state updates (default: false)
// Re-render on every state update
const editor = useEditor({ update: true })

useEditorDerivedValue

Compute and subscribe to derived values from the editor state.
import { useEditorDerivedValue } from '@prosekit/preact'

function StatusBar() {
  const wordCount = useEditorDerivedValue((editor) => {
    return editor.state.doc.textContent.split(/\s+/).length
  })
  
  return <div>Words: {wordCount}</div>
}

useExtension

Dynamically add extensions to the editor.
import { useExtension } from '@prosekit/preact'
import { defineCodeBlock } from '@prosekit/extensions'

function CodeBlockPlugin({ enabled }: { enabled: boolean }) {
  const extension = enabled ? defineCodeBlock() : null
  useExtension(extension)
  return null
}
Options:
  • editor?: Editor - Optional editor instance (defaults to context editor)
  • priority?: Priority - Extension priority

useKeymap

Define keyboard shortcuts.
import { useKeymap } from '@prosekit/preact'

function SavePlugin() {
  useKeymap({
    'Mod-s': () => {
      console.log('Save triggered')
      return true
    }
  })
  return null
}

useDocChange

Execute a callback when the document changes.
import { useDocChange } from '@prosekit/preact'

function AutoSave() {
  useDocChange((editor) => {
    const json = editor.getDocJSON()
    localStorage.setItem('doc', JSON.stringify(json))
  })
  return null
}

useStateUpdate

Execute a callback on every state update (including selection changes).
import { useStateUpdate } from '@prosekit/preact'
import { useState } from 'preact/hooks'

function SelectionTracker() {
  const [selection, setSelection] = useState({ from: 0, to: 0 })
  
  useStateUpdate((editor) => {
    const { from, to } = editor.state.selection
    setSelection({ from, to })
  })
  
  return <div>Selection: {selection.from} - {selection.to}</div>
}

Custom Node Views

Create interactive node views with Preact components:
import { definePreactNodeView } from '@prosekit/preact'
import type { PreactNodeViewProps } from '@prosekit/preact'

function ImageComponent(props: PreactNodeViewProps) {
  const { node, setAttrs } = props
  const src = node.attrs.src
  
  return (
    <img 
      src={src} 
      alt={node.attrs.alt}
      onClick={() => setAttrs({ selected: true })}
    />
  )
}

const imageExtension = definePreactNodeView({
  name: 'image',
  component: ImageComponent,
  contentDOM: false,
})
PreactNodeViewProps:
  • 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 Preact:
import { definePreactMarkView } from '@prosekit/preact'
import type { PreactMarkViewProps } from '@prosekit/preact'

function CommentMark(props: PreactMarkViewProps) {
  const { mark, children } = props
  const commentId = mark.attrs.id
  
  return (
    <span 
      class="comment"
      data-comment-id={commentId}
    >
      {children}
    </span>
  )
}

const commentExtension = definePreactMarkView({
  name: 'comment',
  component: CommentMark,
})

UI Components

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

Autocomplete

import { 
  AutocompletePopover, 
  AutocompleteItem, 
  AutocompleteList, 
  AutocompleteEmpty 
} from '@prosekit/preact/autocomplete'

<AutocompletePopover>
  <AutocompleteList>
    <AutocompleteEmpty>No results</AutocompleteEmpty>
    <AutocompleteItem value="preact">Preact</AutocompleteItem>
    <AutocompleteItem value="react">React</AutocompleteItem>
  </AutocompleteList>
</AutocompletePopover>

Inline Popover

import { InlinePopover } from '@prosekit/preact/inline-popover'

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

Tooltip

import { TooltipRoot, TooltipTrigger, TooltipContent } from '@prosekit/preact/tooltip'

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

Popover

import { PopoverRoot, PopoverTrigger, PopoverContent } from '@prosekit/preact/popover'

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

Block Handle

import { BlockHandleDraggable, BlockHandleAdd, BlockHandlePopover } from '@prosekit/preact/block-handle'

<BlockHandleDraggable />
<BlockHandleAdd />
<BlockHandlePopover>
  {/* Block options */}
</BlockHandlePopover>

Table Handle

import { 
  TableHandleRoot,
  TableHandleColumnRoot,
  TableHandleColumnTrigger,
  TableHandleRowRoot,
  TableHandleRowTrigger 
} from '@prosekit/preact/table-handle'

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

Resizable

import { ResizableRoot, ResizableHandle } from '@prosekit/preact/resizable'

<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/preact'
import { defineBasicExtension } from '@prosekit/extensions'

const extension = defineBasicExtension()

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

function MyComponent() {
  const editor = useEditor()
  // editor.commands has autocomplete for all available commands
  editor.commands.toggleBold()
}

Preact Signals

ProseKit works well with Preact’s signals for fine-grained reactivity:
import { createEditor } from '@prosekit/core'
import { ProseKit, useEditor } from '@prosekit/preact'
import { defineBasicExtension } from '@prosekit/extensions'
import { signal, computed } from '@preact/signals'

const content = signal('')

function Editor() {
  const editor = createEditor({
    extension: defineBasicExtension()
  })
  
  const editorRef = useEditor({ update: true })
  
  const wordCount = computed(() => {
    return content.value.split(/\s+/).length
  })
  
  return (
    <ProseKit editor={editor}>
      <div>Words: {wordCount}</div>
    </ProseKit>
  )
}

Bundle Size Optimization

Preact’s smaller bundle size makes it ideal for performance-critical applications:
// ProseKit + Preact has a significantly smaller bundle than React
import { createEditor } from '@prosekit/core'
import { ProseKit } from '@prosekit/preact'
import { defineBasicExtension } from '@prosekit/extensions'

// Total bundle: ~50KB gzipped (vs ~120KB with React)

React Compatibility

Preact can use React libraries with preact/compat:
// vite.config.ts
import { defineConfig } from 'vite'
import preact from '@preact/preset-vite'

export default defineConfig({
  plugins: [preact()],
  resolve: {
    alias: {
      react: 'preact/compat',
      'react-dom': 'preact/compat'
    }
  }
})

Server-Side Rendering (SSR)

ProseKit supports server-side rendering with Preact:
import { createEditor } from '@prosekit/core'
import { ProseKit } from '@prosekit/preact'
import { defineBasicExtension } from '@prosekit/extensions'
import { useEffect, useState } from 'preact/hooks'

function Editor() {
  const [editor, setEditor] = useState(null)
  
  useEffect(() => {
    setEditor(createEditor({ 
      extension: defineBasicExtension() 
    }))
  }, [])
  
  if (!editor) return null
  
  return (
    <ProseKit editor={editor}>
      {/* Editor components */}
    </ProseKit>
  )
}

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