Skip to content

Get Started

Installation

You can install ProseKit using npm, yarn, or pnpm. Choose the command that corresponds to your package manager:

shell
npm install prosekit
shell
yarn add prosekit
shell
pnpm add prosekit

Minimal Examples

ProseKit is compatible with various JavaScript frameworks and vanilla JavaScript. Here are some minimal examples of how to use ProseKit in different frameworks:

tsx
import 'prosekit/basic/style.css'

import {
  createEditor,
  jsonFromNode,
  type NodeJSON,
} from 'prosekit/core'
import type { ProseMirrorNode } from 'prosekit/pm/model'
import {
  ProseKit,
  useDocChange,
} from 'prosekit/react'
import {
  useCallback,
  useMemo,
} from 'react'

import { defineExtension } from './extension'

export default function Editor({
  defaultContent,
  onDocUpdate,
}: {
  defaultContent?: NodeJSON
  onDocUpdate?: (doc: NodeJSON) => void
}) {
  const editor = useMemo(() => {
    const extension = defineExtension()
    return createEditor({ extension, defaultContent })
  }, [defaultContent])

  const handleDocChange = useCallback(
    (doc: ProseMirrorNode) => onDocUpdate?.(jsonFromNode(doc)),
    [onDocUpdate],
  )
  useDocChange(handleDocChange, { editor })

  return (
    <ProseKit editor={editor}>
      <div className='box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 shadow dark:border-zinc-700 flex flex-col bg-white dark:bg-neutral-900'>
        <div className='relative w-full flex-1 box-border overflow-y-scroll'>
          <div ref={editor.mount} className='ProseMirror box-border min-h-full px-[max(4rem,_calc(50%-20rem))] py-8 outline-none outline-0 [&_span[data-mention="user"]]:text-blue-500 [&_span[data-mention="tag"]]:text-violet-500 [&_pre]:text-white [&_pre]:bg-zinc-800'></div>
        </div>
      </div>
    </ProseKit>
  )
}
vue
<script setup lang="ts">
import 'prosekit/basic/style.css'

import {
  createEditor,
  jsonFromNode,
  type NodeJSON,
} from 'prosekit/core'
import {
  ProseKit,
  useDocChange,
} from 'prosekit/vue'
import {
  ref,
  watchPostEffect,
} from 'vue'

import { defineExtension } from './extension'

const props = defineProps<{
  defaultContent?: NodeJSON
}>()

const emit = defineEmits<{
  docUpdate: [doc: NodeJSON]
}>()

const extension = defineExtension()
const editor = createEditor({ extension, defaultContent: props.defaultContent })

useDocChange(
  (doc) => {
    emit('docUpdate', jsonFromNode(doc))
  },
  { editor },
)

const editorRef = ref<HTMLDivElement | null>(null)
watchPostEffect((onCleanup) => {
  editor.mount(editorRef.value)
  onCleanup(() => editor.unmount())
})
</script>

<template>
  <ProseKit :editor="editor">
    <div class='box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 shadow dark:border-zinc-700 flex flex-col bg-white dark:bg-neutral-900'>
      <div class='relative w-full flex-1 box-border overflow-y-scroll'>
        <div ref="editorRef" class='ProseMirror box-border min-h-full px-[max(4rem,_calc(50%-20rem))] py-8 outline-none outline-0 [&_span[data-mention="user"]]:text-blue-500 [&_span[data-mention="tag"]]:text-violet-500 [&_pre]:text-white [&_pre]:bg-zinc-800' />
      </div>
    </div>
  </ProseKit>
</template>
tsx
import 'prosekit/basic/style.css'

import {
  useCallback,
  useMemo,
} from 'preact/hooks'
import { defineBasicExtension } from 'prosekit/basic'
import {
  createEditor,
  jsonFromNode,
  type NodeJSON,
} from 'prosekit/core'
import type { ProseMirrorNode } from 'prosekit/pm/model'
import {
  ProseKit,
  useDocChange,
} from 'prosekit/preact'

export default function Editor(props: {
  defaultContent?: NodeJSON
  onDocUpdate?: (doc: NodeJSON) => void
}) {
  const editor = useMemo(() => {
    const extension = defineBasicExtension()
    return createEditor({ extension, defaultContent: props.defaultContent })
  }, [])

  const handleDocChange = useCallback(
    (doc: ProseMirrorNode) => props.onDocUpdate?.(jsonFromNode(doc)),
    [props.onDocUpdate],
  )
  useDocChange(handleDocChange, { editor })

  return (
    <ProseKit editor={editor}>
      <div className='box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 shadow dark:border-zinc-700 flex flex-col bg-white dark:bg-neutral-900'>
        <div className='relative w-full flex-1 box-border overflow-y-scroll'>
          <div ref={editor.mount} className='ProseMirror box-border min-h-full px-[max(4rem,_calc(50%-20rem))] py-8 outline-none outline-0 [&_span[data-mention="user"]]:text-blue-500 [&_span[data-mention="tag"]]:text-violet-500 [&_pre]:text-white [&_pre]:bg-zinc-800'></div>
        </div>
      </div>
    </ProseKit>
  )
}
svelte
<script lang="ts">
import 'prosekit/basic/style.css'

import { defineBasicExtension } from 'prosekit/basic'
import {
  createEditor,
  jsonFromNode,
  type NodeJSON,
} from 'prosekit/core'
import {
  ProseKit,
  useDocChange,
} from 'prosekit/svelte'

export let defaultContent: NodeJSON | undefined = undefined
export let onDocUpdate: ((doc: NodeJSON) => void) | undefined = undefined

const extension = defineBasicExtension()
const editor = createEditor({ extension, defaultContent })

useDocChange((doc) => onDocUpdate?.(jsonFromNode(doc)), { editor })

const mount = (element: HTMLElement) => {
  editor.mount(element)
  return { destroy: () => editor.unmount() }
}
</script>

<ProseKit {editor}>
  <div class='box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 shadow dark:border-zinc-700 flex flex-col bg-white dark:bg-neutral-900'>
    <div class='relative w-full flex-1 box-border overflow-y-scroll'>
      <div use:mount class='ProseMirror box-border min-h-full px-[max(4rem,_calc(50%-20rem))] py-8 outline-none outline-0 [&_span[data-mention="user"]]:text-blue-500 [&_span[data-mention="tag"]]:text-violet-500 [&_pre]:text-white [&_pre]:bg-zinc-800'></div>
    </div>
  </div>
</ProseKit>
tsx
import 'prosekit/basic/style.css'

import { defineBasicExtension } from 'prosekit/basic'
import {
  createEditor,
  jsonFromNode,
  type NodeJSON,
} from 'prosekit/core'
import {
  ProseKit,
  useDocChange,
} from 'prosekit/solid'

export default function Editor(props: {
  defaultContent?: NodeJSON
  onDocUpdate?: (doc: NodeJSON) => void
}) {
  const extension = defineBasicExtension()
  const editor = createEditor({
    extension,
    defaultContent: props.defaultContent,
  })

  useDocChange((doc) => props.onDocUpdate?.(jsonFromNode(doc)), { editor })

  return (
    <ProseKit editor={editor}>
      <div class='box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 shadow dark:border-zinc-700 flex flex-col bg-white dark:bg-neutral-900'>
        <div class='relative w-full flex-1 box-border overflow-y-scroll'>
          <div ref={editor.mount} class='ProseMirror box-border min-h-full px-[max(4rem,_calc(50%-20rem))] py-8 outline-none outline-0 [&_span[data-mention="user"]]:text-blue-500 [&_span[data-mention="tag"]]:text-violet-500 [&_pre]:text-white [&_pre]:bg-zinc-800'></div>
        </div>
      </div>
    </ProseKit>
  )
}

Let's go through the code to understand what's going on.

Extensions

All customizations in ProseKit are done through extensions. Here we are using the defineBasicExtension function to return a basic extension that provides the most common features.

ts
const 
extension
=
defineBasicExtension
()

Editor

The createEditor function creates an editor instance.

ts
const 
editor
=
createEditor
({
extension
})

You need to mount the editor to the DOM by calling the editor.mount(element) method. When the editor is unmounted, you should call editor.unmount() or editor.mount(null) to clean up the editor. Check out the minimal examples above to see how to mount the editor in different frameworks.

Data Persistence

The editor's document is stored in JSON, using the NodeJSON format. The <Editor> component requires two properties: defaultDoc and onDocChange. defaultDoc is the initial content shown when the editor loads. onDocChange is a callback function that is executed whenever the document changes.

ProseKit also provides utilities for converting the editor's document to and from HTML. Be aware that converting to HTML format may not capture all details. See the examples below to see how to use these tools:

Styling

ProseKit is headless, giving you full control over your editor's appearance. However, to help you in getting started, we offers two basic stylesheets.

ts
import 'prosekit/basic/style.css'
ts
import 'prosekit/basic/typography.css'

The prosekit/basic/style.css file provides essential styles to ensure the editor displays correctly.

The prosekit/basic/typography.css file offers basic typography styles for the editor, like margins, paddings, and font sizes. This is optional, and you can exclude it if you wish to use your own styles.