An original field-report editor needs a small Bold button beside a selected finding. The author selects “water entering through the north seal,” emphasizes that finding, and continues writing. A permanently visible form toolbar and a menu that opens on an empty line solve different interaction problems.
Attach one menu to the intended editor
For an existing vanilla JavaScript Tiptap 3 integration, create the menu element before initializing the editor:
<div id="finding-menu" style="visibility:hidden;position:absolute">
<button type="button" id="finding-bold">Bold</button>
</div>
<div id="field-report"></div>
Add this configured extension to the application's existing extension list:
import BubbleMenu from '@tiptap/extension-bubble-menu';
const findingMenu = BubbleMenu.configure({
pluginKey: 'findingFormatting',
element: document.querySelector('#finding-menu'),
options: {
placement: 'top',
offset: 8,
flip: true,
shift: true
}
});
Keep the existing base document extensions and Bold support. Use visibility:hidden for the initial hidden state: the current plugin implementation changes visibility when showing the element. An inline display:none would still prevent it from appearing. This fragment configures a menu; it does not create the editor. After initialization, with editor referring to that field-report instance, connect the button:
document.querySelector('#finding-bold')
.addEventListener('click', () => {
editor.chain().focus().toggleBold().run();
});
Restoring editor focus lets the command act on its document selection. type="button" prevents an enclosing form from treating the formatting control as a submission. In a component, remove application event listeners when the component unmounts.
Use the matching framework entry point
The current React component is imported from @tiptap/react/menus; the Vue 3 component comes from @tiptap/vue-3/menus. Pass the initialized editor to the component and let it own its menu DOM. Follow that route instead of copying the vanilla extension registration into the same component.
If several editor instances appear on one page, confirm that each menu receives its own editor and element. For multiple BubbleMenus within one editor, assign distinct pluginKey values. Otherwise, a generic selector or shared plugin identity can connect the wrong control to the report.
Diagnose visibility before adjusting coordinates
Select the finding first. A menu intended for selected text need not appear at a collapsed cursor. Inspect any custom shouldShow callback if the selection is valid but no menu appears. Check editor editability, the actual menu element, and CSS that may keep it hidden.
When the menu appears in the wrong place, inspect the menu's containing layout, scrolling container, and clipping rules. appendTo can choose a different DOM destination. Moving the menu changes its layout context, so verify the resulting coordinates instead of assuming that appending to document.body fixes every editor.
If the menu changes size after rendering, the documented positioning refresh uses its plugin key:
editor.commands.setMeta('findingFormatting', 'updatePosition');
Use that after a real size change. A positioning refresh cannot make a missing selection eligible or repair an unregistered formatting extension.
Check selection, dismissal, and a narrow report
In your application, select only the north-seal finding and apply Bold. Confirm that neighboring text stays unchanged, the selection can be adjusted, and the menu remains reachable near the edge of a narrow editor. Move the cursor into ordinary prose and check that the contextual menu no longer obscures the next sentence. Keep an accessible route to formatting for people who cannot comfortably operate a floating control.
Save and reopen the report through its normal path. The emphasis belongs in the document; the menu's position and visibility do not. The Tiptap document integration covers document capture, while saved text alignment covers a separate block-level choice. Finish by reading the finding in the reader view, where the authoring menu should not cover the evidence.