Skip to main content
ProseKit provides first-class support for React through the @prosekit/react package, offering React-specific components, hooks, and utilities for building powerful editors.

Installation

npm install prosekit @prosekit/react
Peer dependencies:
  • react >= 18.2.0
  • react-dom >= 18.2.0

Quick Start

Create a basic editor with React:
import { createEditor } from '@prosekit/core'
import { ProseKit, useEditor } from '@prosekit/react'
import { defineBasicExtension } from '@prosekit/extensions'

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

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

function EditorContent() {
  const editor = useEditor()
  return <div ref={(el) => el && editor.mount(el)} />
}

Core Components

ProseKit

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

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

Essential Hooks

useEditor

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

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 })
The update option doesn’t work with React Compiler. If you’re using React Compiler, use useEditorDerivedValue instead.

useEditorDerivedValue

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

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/react'
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/react'

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/react'

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/react'

function SelectionTracker() {
  useStateUpdate((editor) => {
    const { from, to } = editor.state.selection
    console.log('Selection:', { from, to })
  })
  return null
}

Custom Node Views

Create interactive node views with React components:
import { defineReactNodeView } from '@prosekit/react'
import type { ReactNodeViewProps } from '@prosekit/react'

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

const imageExtension = defineReactNodeView({
  name: 'image',
  component: ImageComponent,
  contentDOM: false,
})
ReactNodeViewProps:
  • 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 React:
import { defineReactMarkView } from '@prosekit/react'
import type { ReactMarkViewProps } from '@prosekit/react'

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

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

UI Components

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

Autocomplete

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

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

Inline Popover

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

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

Tooltip

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

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

Popover

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

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

Block Handle

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

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

Table Handle

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

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

Resizable

import { ResizableRoot, ResizableHandle } from '@prosekit/react/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/react'
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()
}

Server-Side Rendering (SSR)

ProseKit supports server-side rendering with Next.js and other React frameworks:
'use client' // For Next.js App Router

import { createEditor } from '@prosekit/core'
import { ProseKit } from '@prosekit/react'
import { useMemo } from 'react'

function Editor() {
  const editor = useMemo(() => {
    return createEditor({ extension: defineBasicExtension() })
  }, [])
  
  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