@prosemirror-adapter/react
React adapter for ProseMirror.
Example
You can view the example in prosemirror-adapter/examples/react.

Getting Started
Install the package
npm install @prosemirror-adapter/react
Wrap your component with provider
import { ProsemirrorAdapterProvider } from '@prosemirror-adapter/react'
import { YourAwesomeEditor } from 'somewhere'
export const Component = () => {
return (
<ProsemirrorAdapterProvider>
<YourAwesomeEditor />
</ProsemirrorAdapterProvider>
)
}
Play with node view
In this section we will implement a node view for paragraph node.
Build component for node view
import { useNodeViewContext } from '@prosemirror-adapter/react'
const Paragraph = () => {
const { contentRef, selected } = useNodeViewContext()
return <div style={{ outline: selected ? 'blue solid 1px' : 'none' }} role="presentation" ref={contentRef} />
}
Bind node view components with prosemirror
import { useNodeViewFactory } from '@prosemirror-adapter/react'
import type { FC } from 'react'
import { useCallback, useRef } from 'react'
import { Paragraph } from './Paragraph'
export const YourAwesomeEditor: FC = () => {
const nodeViewFactory = useNodeViewFactory()
const editorRef = useCallback(
(element: HTMLDivElement) => {
if (!element || element.firstChild)
return
const editorView = new EditorView(element, {
state: YourProsemirrorEditorState,
nodeViews: {
paragraph: nodeViewFactory({
component: Paragraph,
as: 'div',
contentAs: 'p',
}),
}
})
},
[nodeViewFactory],
)
return <div className="editor" ref={editorRef} />
}
🚀 Congratulations! You have built your first react node view with prosemirror-adapter.
Play with mark view
In this section we will implement a mark view for links that changes color periodically.
Build component for mark view
import { useEffect, useState } from 'react'
import { useMarkViewContext } from '@prosemirror-adapter/react'
const colors = [
'#f06292', '#ba68c8', '#9575cd', '#7986cb', '#64b5f6',
'#4fc3f7', '#4dd0e1', '#4db6ac', '#81c784', '#aed581',
'#ffb74d', '#ffa726', '#ff8a65', '#d4e157', '#ffd54f',
'#ffecb3',
]
function pickRandomColor() {
return colors[Math.floor(Math.random() * colors.length)]
}
export function Link() {
const [color, setColor] = useState(colors[0])
const { mark, contentRef } = useMarkViewContext()
const href = mark.attrs.href as string
const title = mark.attrs.title as string | null
useEffect(() => {
const interval = setInterval(() => {
setColor(pickRandomColor())
}, 1000)
return () => clearInterval(interval)
}, [])
return (
<a
href={href}
ref={contentRef}
style={{ color, transition: 'color 1s ease-in-out' }}
title={title || undefined}
>
</a>
)
}
Bind mark view components with prosemirror
import { useMarkViewFactory } from '@prosemirror-adapter/react'
import type { FC } from 'react'
import { useCallback } from 'react'
export const YourAwesomeEditor: FC = () => {
const markViewFactory = useMarkViewFactory()
const editorRef = useCallback(
(element: HTMLDivElement) => {
if (!element || element.firstChild)
return
const editorView = new EditorView(element, {
state: EditorState.create({
schema: YourProsemirrorSchema,
plugins: [
new Plugin({
props: {
markViews: {
link: markViewFactory({
component: Link,
}),
},
},
}),
]
})
})
},
[markViewFactory],
)
return <div className="editor" ref={editorRef} />
}
🚀 Congratulations! You have built your first react mark view with prosemirror-adapter.
Play with plugin view
In this section we will implement a plugin view that will display the size of the document.
import { usePluginViewContext } from '@prosemirror-adapter/react'
const Size = () => {
const { view } = usePluginViewContext()
const size = view.state.doc.nodeSize
return <div>Size for document: {size}</div>
}
Bind plugin view components with prosemirror
import { usePluginViewFactory } from '@prosemirror-adapter/react'
import type { FC } from 'react'
import { useCallback, useRef } from 'react'
import { Plugin } from 'prosemirror-state'
import { Paragraph } from './Paragraph'
export const YourAwesomeEditor: FC = () => {
const pluginViewFactory = usePluginViewFactory()
const editorRef = useCallback(
(element: HTMLDivElement) => {
if (!element || element.firstChild)
return
const editorView = new EditorView(element, {
state: EditorState.create({
schema: YourProsemirrorSchema,
plugins: [
new Plugin({
view: pluginViewFactory({
component: Size,
}),
}),
]
})
})
},
[pluginViewFactory],
)
return <div className="editor" ref={editorRef} />
}
🚀 Congratulations! You have built your first react plugin view with prosemirror-adapter.
Play with widget view
In this section we will implement a widget view that will add hashes for heading when selected.
import { useWidgetViewContext } from '@prosemirror-adapter/react'
export const Hashes = () => {
const { spec } = useWidgetViewContext()
const level = spec?.level
const hashes = Array(level || 0).fill('#').join('')
return <span style={{ color: 'blue', marginRight: 6 }}>{hashes}</span>
}
Bind widget view components with prosemirror
import { useWidgetViewFactory } from '@prosemirror-adapter/react'
import type { FC } from 'react'
import { useCallback, useRef } from 'react'
import { Plugin } from 'prosemirror-state'
import { Hashes } from './Hashes'
export const YourAwesomeEditor: FC = () => {
const widgetViewFactory = useWidgetViewFactory()
const editorRef = useCallback(
(element: HTMLDivElement) => {
if (!element || element.firstChild)
return
const getHashWidget = widgetViewFactory({
as: 'i',
component: Hashes,
})
const editorView = new EditorView(element, {
state: EditorState.create({
schema: YourProsemirrorSchema,
plugins: [
new Plugin({
props: {
decorations(state) {
const { $from } = state.selection
const node = $from.node()
if (node.type.name !== 'heading')
return DecorationSet.empty
const widget = getHashWidget($from.before() + 1, {
side: -1,
level: node.attrs.level,
})
return DecorationSet.create(state.doc, [widget])
},
},
}),
]
})
})
},
[widgetViewFactory],
)
return <div className="editor" ref={editorRef} />
}
🚀 Congratulations! You have built your first react widget view with prosemirror-adapter.
API
Node view API
useNodeViewFactory: () => (options: NodeViewFactoryOptions) => NodeView
type DOMSpec = string | HTMLElement | ((node: Node) => HTMLElement)
interface NodeViewFactoryOptions {
component: ReactComponent
as?: DOMSpec
contentAs?: DOMSpec
update?: (node: Node, decorations: readonly Decoration[], innerDecorations: DecorationSource) => boolean | void
ignoreMutation?: (mutation: ViewMutationRecord) => boolean | void
selectNode?: () => void
deselectNode?: () => void
setSelection?: (anchor: number, head: number, root: Document | ShadowRoot) => void
stopEvent?: (event: Event) => boolean
destroy?: () => void
onUpdate?: () => void
}
useNodeViewContext: () => NodeViewContext
interface NodeViewContext {
contentRef: NodeViewContentRef
view: EditorView
getPos: () => number | undefined
setAttrs: (attrs: Attrs) => void
node: Node
decorations: readonly Decoration[]
innerDecorations: DecorationSource
selected: boolean
}
Mark view API
useMarkViewFactory: () => (options: MarkViewFactoryOptions) => MarkView
type MarkViewDOMSpec = string | HTMLElement | ((mark: Mark) => HTMLElement)
interface MarkViewFactoryOptions {
component: ReactComponent
as?: MarkViewDOMSpec
contentAs?: MarkViewDOMSpec
destroy?: () => void
}
useMarkViewContext: () => MarkViewContext
interface MarkViewContext {
contentRef: MarkViewContentRef
view: EditorView
mark: Mark
inline: boolean
}
Plugin view API
usePluginViewFactory: () => (options: PluginViewFactoryOptions) => PluginView
interface PluginViewFactoryOptions {
component: ReactComponent
root?: (viewDOM: HTMLElement) => HTMLElement
update?: (view: EditorView, prevState: EditorState) => void
destroy?: () => void
}
usePluginViewContext: () => PluginViewContext
interface PluginViewContext {
view: EditorView
prevState?: EditorState
}
Widget view API
useWidgetViewFactory: () => (options: WidgetViewFactoryOptions) => WidgetDecorationFactory
type WidgetDecorationFactory = (pos: number, spec?: WidgetDecorationSpec) => Decoration
interface WidgetViewFactoryOptions {
component: ReactComponent
as: string | HTMLElement
}
useWidgetViewContext: () => WidgetViewContext
interface WidgetViewContext {
view: EditorView
getPos: () => number | undefined
spec?: WidgetDecorationSpec
}
Contributing
Follow our contribution guide to learn how to contribute to prosemirror-adapter.
License
MIT