API Reference
Menu Engine
Build floating menus, bubble menus, and toolbars using Arkpad's positioning engine.
The Menu Engine is a centralized positioning service for floating UI elements. It lives in the Core package so it works across all frameworks with maximum performance.
How It Works
The engine uses a 3-step pipeline:
1. Headless Math
Engine calculates coordinates (DOMRects) for selections
and insertion points using ProseMirror's selection API.
2. Reactive Storage
Coordinates stored in the editor's global storage as
CSS variables (--menu-x, --menu-y).
3. Atomic Updates
Framework hooks (useMenuPositioner) subscribe to storage
updates and apply GPU-accelerated CSS transforms.Benefits
- Zero Flicker — Updates synced with
requestAnimationFrame, not React re-renders - Zero Layout Thrashing — Decouples editor rendering from menu rendering completely
- Unified Logic — One engine handles bubble menus, table selection handles, and slash commands
Building a Bubble Menu
A bubble menu appears when text is selected:
function BubbleMenu({ editor }) {
if (editor.getSelection().empty) return null; // Hide when no selection
return (
<div
className="bubble-menu"
style={{
position: "absolute",
left: `var(--menu-x)`,
top: `var(--menu-y)`,
}}
>
<button onClick={() => editor.runCommand("toggleBold")}>Bold</button>
<button onClick={() => editor.runCommand("toggleItalic")}>Italic</button>
<button onClick={() => editor.runCommand("toggleLink", { href: prompt("URL:") })}>
Link
</button>
</div>
);
}Building a Floating Toolbar
A floating/top toolbar is always visible:
function FixedToolbar({ editor }) {
return (
<div className="toolbar">
<button
className={editor.isActive("strong") ? "active" : ""}
onClick={() => editor.runCommand("toggleBold")}
>
<strong>B</strong>
</button>
<button
className={editor.isActive("em") ? "active" : ""}
onClick={() => editor.runCommand("toggleItalic")}
>
<em>I</em>
</button>
<button
className={editor.isActive("heading", { level: 2 }) ? "active" : ""}
onClick={() => editor.runCommand("toggleHeading", { level: 2 })}
>
H2
</button>
</div>
);
}Registering Menus in Extensions
Any extension can register a menu by implementing the addMenu method:
export const MyExtension = Extension.create({
addMenu() {
return {
type: "bubble",
shouldShow: ({ state }) => !state.selection.empty,
};
},
});This allows extensions to self-register their own contextual menus without manual wiring.
Smart Bar Integration
The Smart Bar is Arkpad's advanced contextual floating menu. It extends the Menu Engine with:
- Slot-based API — Define multiple groups that morph based on editor state
- CSS Variable Positioning — 120fps movement using
--sb-xand--sb-y - Adaptive Flip — Automatically flips position to stay within viewport
import { SmartBar, EditorButton } from "@arkpad/react";
function MySmartBar() {
return (
<SmartBar>
<SmartBar.Group showIf={(editor) => !editor.getSelection().empty}>
<EditorButton command="toggleBold" name="strong">
Bold
</EditorButton>
<EditorButton command="toggleItalic" name="em">
Italic
</EditorButton>
</SmartBar.Group>
<SmartBar.Group showIf={(editor) => editor.isActive("table")}>
<EditorButton command="addColumnAfter">Add Column</EditorButton>
<EditorButton command="addRowAfter">Add Row</EditorButton>
</SmartBar.Group>
</SmartBar>
);
}See the Smart Bar guide for full details.