Links and context menus
Configure link behavior and add application actions to the built-in Editor context menu.
The built-in UI can create and edit hyperlinks, show a link popover, and combine SuperDoc's default context menu with application-owned actions.
Configure both interaction surfaces
This example keeps the default menu, adds one selection-aware action, and replaces the clicked-link popover with framework-neutral DOM:
<div id="toolbar"></div>
<div id="editor"></div>
<script type="module" src="/src/main.ts"></script>
import { SuperDoc, type ContextMenuConfig, type LinkPopoverResolver } from 'superdoc';
import 'superdoc/style.css';
const resolveLinkPopover: LinkPopoverResolver = ({ href }) => ({
type: 'external',
render: ({ container, closePopover }) => {
const link = document.createElement('a');
link.href = href;
link.target = '_blank';
link.rel = 'noopener noreferrer';
link.textContent = 'Open link';
const close = document.createElement('button');
close.type = 'button';
close.textContent = 'Close';
close.addEventListener('click', closePopover);
container.append(link, close);
return { destroy: () => close.removeEventListener('click', closePopover) };
},
});
const contextMenu = {
includeDefaultItems: true,
customItems: [
{
id: 'application-actions',
items: [
{
id: 'copy-selection-to-workflow',
label: 'Copy selection to workflow',
showWhen: ({ hasSelection }) => hasSelection,
// `onSelect`, not the v1 `action` callback: v2 cannot invoke `action`
// because its first argument is a ProseMirror Editor this runtime does
// not have, so an `action`-only item warns once and dismisses.
//
// `context` is the snapshot captured when the menu opened, and it is
// null when none was captured. `selectedText` is read synchronously to
// keep the click's user activation, which `navigator.clipboard`
// requires; it is empty when a worker-backed read had not settled by
// click time, so copy only when it carries text. Awaiting
// `selectedTextSettled` would return the accurate text but spend the
// activation the clipboard write needs.
// `onSelect` returns `void | Promise<void>`, so returning the write
// hands the rejection to the runtime instead of leaving an unhandled
// one when clipboard access is denied. The write is still initiated
// synchronously inside the gesture, which is what the permission
// check requires.
onSelect: ({ context }) => {
if (!context?.selectedText) return;
return navigator.clipboard.writeText(context.selectedText);
},
},
],
},
],
} satisfies ContextMenuConfig;
const superdoc = new SuperDoc({
selector: '#editor',
document: '/contract.docx',
ui: {
// A toolbar renders only once it has somewhere to mount. Naming groups
// without a container leaves the handle available and the toolbar absent.
toolbar: { container: '#toolbar', groups: { center: ['link'] } },
contextMenu,
},
// `popoverResolver` has no `ui` equivalent yet: the link popover reads it
// from `modules.links`, so this one stays where the runtime looks for it.
modules: {
links: { popoverResolver: resolveLinkPopover },
},
});
window.addEventListener('beforeunload', () => superdoc.destroy());
modules.links.popoverResolver runs synchronously when a link is clicked. Return default to use SuperDoc's popover, none to suppress it, or external to render with your own framework or DOM. The external renderer must return a cleanup function when it installs listeners or mounts a framework root. This resolver is the one built-in UI setting that has no ui equivalent yet, so it stays under modules.links; ui: { linkPopover: false } suppresses the popover entirely.
ui.contextMenu.customItems adds sections to both the built-in right-click menu and the slash menu. SuperDoc calls showWhen() with the current context; return true when the action is relevant. Keep application actions narrow and avoid reconstructing document positions from selectionStart or selectionEnd; use the Document API and public selection targets for document work.
Separate navigation from mutation
Opening a link is browser navigation. Creating, changing, and removing hyperlinks are document mutations. The built-in toolbar handles those mutations against the live selection and respects viewing mode. For programmatic hyperlink changes, use the generated Document API hyperlink operations instead of simulating toolbar clicks.
Context-menu visibility is not authorization. Server-side access and collaboration policy remain the application's responsibility.