Seeking Alternative to Tiptap's React-Based NodeView for Hover-Activated Sidebar Menu in Paragraph Nodes

I am currently developing a paragraph node extension using tiptap, which relies on NodeView based on React. In my implementation, each paragraph node displays a sidebar menu button when hovered over, as shown in the following diagram.

However, I am facing severe performance issues with tiptap’s React-based NodeView rendering, particularly when the line count increases. The text input becomes laggy, severely affecting the user experience. The latest version of Tiptap’s NodeView has concerning performance issues.Since the progress on resolving this bug on tiptap is slow, I am considering other solutions.

I’ve studied the plugin view used in ProseMirror Tooltip example, but I am unsure if it’s feasible for my case. I need to track the top-level node where the mouse is hovering and pass this information to the plugin view, similar to how a node view can get real-time node information.

Here’s a snippet of my code:

key: new PluginKey("TestP"),
props: {
  handleDOMEvents: {
    mouseover: (view, event) => {
      console.log("Mouse over event: ", event);
      setTimeout(() => {
        const targetElement =;
        const pos = view.posAtCoords({
          left: event.clientX,
          top: event.clientY,
      }, 0);

But I found this approach is not viable, as posAtCoords returns the closest node based on coordinates, which might be from the previous or current line. Are there other feasible solutions to implement this feature?

What I’ve tried:

  • Using tiptap’s NodeView with React, but faced performance issues.
  • Tried implementing using ProseMirror Plugin View and using handleDOMEvents.

What I’m looking for:

  • A feasible solution to implement a sidebar menu button on hover for each paragraph node without performance issues.
  • An alternative to tiptap’s NodeView based on React, if possible.

Since my entire project is based on Next.js (React) + Tiptap, I can’t switch to other dependencies. (In fact, I’ve also developed a version based on Vue 3 + Tiptap, but Vue 3 falls short in terms of ecosystem richness compared to React, so I chose Next.js for its more robust ecosystem.)

Thank you for your help.

I’ve found a method to improve performance by handling the mouseEnter event listener in nodeView using vanilla JavaScript. I then use the Singleton pattern to pass data such as the node, getPos, and Editor from nodeView to React components for further processing. For tasks in the React component that require ProseMirror processing (like setting the background color of a selected block), I utilize Decorations. This approach bypasses the low-performance nodeView options in tiptap, resulting in a significant performance boost.

I’m also doing the same things, want to make a side menu like this, can you share your source code for me to learn?

Sorry, I can’t share my source code as it’s part of a commercial project for my company. However, I can offer some basic implementation ideas. The biggest challenge in implementing a sidebar tool floating window is choosing a flexible system that supports edge collision detection for floating UI elements. I’ve been using, which is an excellent library for controlling floating elements. It supports both native JavaScript and frameworks like React and Vue, although it works best with React. and tippy.js are created by the same author. Currently, tippy.js has stopped receiving updates, and the actively maintained project is recommend taking a systematic approach to learning how to use this library, and then you’ll know how to create a sidebar tool floating window.

about For tasks in the React component that require ProseMirror processing (like setting the background color of a selected block), I utilize Decorations.

can u give some demo code?

For example, to implement highlighting, you can set up a highlight Plugin. This plugin requires parameters like Decoration and the position information of the selection area you wish to highlight. To trigger the highlight, you can call:, { add: true }); editor.view.dispatch(tr);

export const pluginKey = new PluginKey<any>("highlightSelection");

const HighlightSelection = Extension.create({
  name: "highlightSelection",

  addProseMirrorPlugins() {
    return [
      new Plugin({
        key: pluginKey,
        state: {
          init(): any {
            return { decorations: DecorationSet.empty, savedSelection: null };
          apply(tr: Transaction, prev: any): any {
            const action = tr.getMeta(pluginKey);
            let decorations = prev.decorations;
            let savedSelection = prev.savedSelection;
            if (action && action.add) {
              savedSelection = tr.selection;
              decorations = DecorationSet.create(tr.doc, [
                Decoration.inline(savedSelection.from,, {
                  class: "selected-text",
            } else if (action && action.remove) {
              decorations = DecorationSet.empty;
              savedSelection = null;

            return { decorations, savedSelection };
        props: {
          decorations(state): DecorationSet {
            return pluginKey.getState(state).decorations;
          handleDOMEvents: {
            focus: (view: EditorView): boolean => {
              const savedSelection = pluginKey.getState(
              if (savedSelection) {
        , { remove: true })

              return false;

export default HighlightSelection;

Suppose I set up my sidemenu by using absolute positioning, but the editor does not have a relevant context such as selection. When I know which dom node my hover is, how should I configure to make bold or the plug-in you wrote take effect?