Skip to content

Views

A view customizes how the editor renders a node or mark in the DOM. For most schemas the toDOM you wrote in the spec is enough. Reach for a view when you need:

  • Interactive content (buttons, inputs, dropdowns inside a node).
  • Live data (an embed that fetches metadata, a widget that updates on selection changes).
  • Per-instance state that ProseMirror's selection model can't express.

defineNodeView takes a name and a ProseMirror NodeViewConstructor.

import { defineNodeViewfunction defineNodeView(options: NodeViewOptions): Extension } from 'prosekit/core'

const myNodeViewconst myNodeView: Extension<ExtensionTyping<any, any, any>> = defineNodeViewfunction defineNodeView(options: NodeViewOptions): Extension({
  nameNodeViewOptions.name: string: 'image',
  constructorNodeViewOptions.constructor: NodeViewConstructor: (nodenode: Node) => {
    const domconst dom: HTMLImageElement = documentvar document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
.createElementDocument.createElement<"img">(tagName: "img", options?: ElementCreationOptions): HTMLImageElement (+2 overloads)
In an HTML document, the **`document.createElement()`** method creates the HTML element specified by localName, or an HTMLUnknownElement if localName isn't recognized. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/createElement)
('img')
domconst dom: HTMLImageElement.srcHTMLImageElement.src: string
The **`src`** property of the HTMLImageElement interface specifies the image to display in the <img> element. It reflects the <img> element's src content attribute. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLImageElement/src)
= String
var String: StringConstructor
(value?: any) => string
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
(nodenode: Node.attrsNode.attrs: Attrs
An object mapping attribute names to values. The kind of attributes allowed and required are [determined](https://prosemirror.net/docs/ref/#model.NodeSpec.attrs) by the node type.
.srcAttrs[string]: any ?? '')
return { domNodeView.dom: HTMLElement
The outer DOM node that represents the document node.
}
}, })

In practice you'll almost always use the framework wrapper instead of the raw helper. Each integration ships a define<Framework>NodeView that lets you write the view as a component:

FrameworkWrapper
ReactdefineReactNodeView
VuedefineVueNodeView
PreactdefinePreactNodeView
SveltedefineSvelteNodeView
SoliddefineSolidNodeView
import { defineReactNodeView, type ReactNodeViewProps } from 'prosekit/react'

function ImageView(props: ReactNodeViewProps) {
  const src = String(props.node.attrs.src ?? '')
  return <img src={src} alt={String(props.node.attrs.alt ?? '')} />
}

const extension = defineReactNodeView({
  name: 'image',
  component: ImageView,
})

ReactNodeViewProps (and its peers in other frameworks) gives you node, view, getPos, setAttrs, decorations, and selected.

defineMarkView and define<Framework>MarkView do the same job for marks. Use them sparingly, since most marks are perfectly served by a toDOM in the mark spec.

A typical use case is a comment or annotation mark that needs a popover when its text is selected.

Decorations sit on top of the document without changing the schema. They're great for showing collaborator cursors, search highlights, or inline error squiggles. Decorations live inside ProseMirror plugins; register them with definePlugin.

import { definePlugin
function definePlugin(plugin: Plugin | Plugin[] | ((context: {
    schema: Schema;
}) => Plugin | Plugin[])): PlainExtension
Adds a ProseMirror plugin to the editor.
@paramplugin - The ProseMirror plugin to add, or an array of plugins, or a function that returns one or multiple plugins.@public
} from 'prosekit/core'
import { Pluginclass Plugin<PluginState = any>
Plugins bundle functionality that can be added to an editor. They are part of the [editor state](https://prosemirror.net/docs/ref/#state.EditorState) and may influence that state and the view that contains it.
} from 'prosekit/pm/state'
import { Decorationclass Decoration
Decoration objects can be provided to the view through the [`decorations` prop](https://prosemirror.net/docs/ref/#view.EditorProps.decorations). They come in several variants—see the static members of this class for details.
, DecorationSetclass DecorationSet
A collection of [decorations](https://prosemirror.net/docs/ref/#view.Decoration), organized in such a way that the drawing algorithm can efficiently use and compare them. This is a persistent data structure—it is not modified, updates create a new value.
} from 'prosekit/pm/view'
const highlightconst highlight: PlainExtension = definePlugin
function definePlugin(plugin: Plugin | Plugin[] | ((context: {
    schema: Schema;
}) => Plugin | Plugin[])): PlainExtension
Adds a ProseMirror plugin to the editor.
@paramplugin - The ProseMirror plugin to add, or an array of plugins, or a function that returns one or multiple plugins.@public
(() => {
return new Pluginnew Plugin<any>(spec: PluginSpec<any>): Plugin<any>
Create a plugin.
({
propsPluginSpec<any>.props?: EditorProps<Plugin<any>> | undefined
The [view props](https://prosemirror.net/docs/ref/#view.EditorProps) added by this plugin. Props that are functions will be bound to have the plugin instance as their `this` binding.
: {
decorationsEditorProps<Plugin<any>>.decorations?: ((this: Plugin<any>, state: EditorState) => DecorationSource | null | undefined) | undefined
A set of [document decorations](https://prosemirror.net/docs/ref/#view.Decoration) to show in the view.
(statestate: EditorState) {
return DecorationSetclass DecorationSet
A collection of [decorations](https://prosemirror.net/docs/ref/#view.Decoration), organized in such a way that the drawing algorithm can efficiently use and compare them. This is a persistent data structure—it is not modified, updates create a new value.
.createDecorationSet.create(doc: Node, decorations: Decoration[]): DecorationSet
Create a set of decorations, using the structure of the given document. This will consume (modify) the `decorations` array, so you must make a copy if you want need to preserve that.
(statestate: EditorState.docEditorState.doc: Node
The current document.
, [
Decorationclass Decoration
Decoration objects can be provided to the view through the [`decorations` prop](https://prosemirror.net/docs/ref/#view.EditorProps.decorations). They come in several variants—see the static members of this class for details.
.inline
Decoration.inline(from: number, to: number, attrs: DecorationAttrs, spec?: {
    inclusiveStart?: boolean;
    inclusiveEnd?: boolean;
    [key: string]: any;
}): Decoration
Creates an inline decoration, which adds the given attributes to each inline node between `from` and `to`.
(0, statestate: EditorState.docEditorState.doc: Node
The current document.
.contentNode.content: Fragment
A container holding the node's children.
.sizeFragment.size: number
The size of the fragment, which is the total of the size of its content nodes.
, { classclass?: string | undefined
A CSS class name or a space-separated set of class names to be _added_ to the classes that the node already had.
: 'highlight' }),
]) }, }, }) })

The Plugin, Decoration, and DecorationSet imports come from prosekit/pm, which re-exports the underlying ProseMirror packages.

  • If the only goal is custom CSS, pass a class: attribute from toDOM instead.
  • If the goal is a non-editable pill (e.g., a chip), see if a regular inline node with atom: true is enough.
  • If you need to render React/Vue/etc. outside the document (a toolbar, a slash menu), use a normal component from Components. Node views are only for in-document UI.