Link Popover
A fully accessible link popover for Tiptap editors. Easily add, edit, and remove links with a user-friendly popover interface that supports keyboard shortcuts and flexible customization options.
Installation
Add the component via the Tiptap CLI:
npx @tiptap/cli@latest add link-popoverComponents
<LinkPopover /> 
 A prebuilt React component that provides a complete link editing interface in a popover.
Usage
import { EditorContent, EditorContext, useEditor } from '@tiptap/react' import { StarterKit } from '@tiptap/starter-kit' import { Link } from '@/components/tiptap-extension/link-extension' import { LinkPopover } from '@/components/tiptap-ui/link-popover'  import '@/components/tiptap-node/paragraph-node/paragraph-node.scss'  export default function MyEditor() {  const editor = useEditor({  immediatelyRender: false,  extensions: [StarterKit, Link.configure({ openOnClick: false })],  content: `  <p>Click the button to open the link popover.</p>  <p><a href="https://www.tiptap.dev">Tiptap</a></p>  `,  })   return (  <EditorContext.Provider value={{ editor }}>  <LinkPopover  editor={editor}  hideWhenUnavailable={true}  autoOpenOnLinkActive={true}  onSetLink={() => console.log('Link set!')}  onOpenChange={(isOpen) => console.log('Popover opened:', isOpen)}  />   <EditorContent editor={editor} role="presentation" />  </EditorContext.Provider>  ) }Props
| Name | Type | Default | Description | 
|---|---|---|---|
| editor | Editor | null | undefined | The Tiptap editor instance | 
| hideWhenUnavailable | boolean | false | Hides the button when link functionality is not available | 
| onSetLink | () => void | undefined | Callback fired after a link is successfully set | 
| onOpenChange | (isOpen: boolean) => void | undefined | Callback fired when the popover opens or closes | 
| autoOpenOnLinkActive | boolean | true | Whether to automatically open when a link is active | 
<LinkButton /> 
 A standalone link button component for triggering link functionality.
Usage
<LinkButton onClick={handleClick} aria-label="Add link">  Custom Link Content </LinkButton><LinkContent /> 
 A standalone component that renders the link editing interface without the popover wrapper.
Usage
<LinkContent editor={editor} />Hooks
useLinkPopover() 
 A custom hook to build your own link interface with full control over rendering and behavior.
Usage
function MyLinkButton() {  const { isVisible, canSet, isActive, url, setUrl, setLink, removeLink, label, Icon } =  useLinkPopover({  editor: myEditor,  hideWhenUnavailable: true,  onSetLink: () => console.log('Link set!'),  })   if (!isVisible) return null   return (  <div>  <button onClick={setLink} disabled={!canSet} aria-label={label} aria-pressed={isActive}>  <Icon />  {label}  </button>  <input  type="url"  value={url}  onChange={(e) => setUrl(e.target.value)}  placeholder="Enter URL..."  />  <button onClick={removeLink}>Remove</button>  </div>  ) }Props
| Name | Type | Default | Description | 
|---|---|---|---|
| editor | Editor | null | undefined | The Tiptap editor instance | 
| hideWhenUnavailable | boolean | false | Hides functionality if link cannot be applied | 
| onSetLink | () => void | undefined | Callback fired after setting a link | 
Return Values
| Name | Type | Description | 
|---|---|---|
| isVisible | boolean | Whether the link functionality should be rendered | 
| canSet | boolean | If a link can be set in the current context | 
| isActive | boolean | If a link is currently active/selected | 
| url | string | Current URL value for the link | 
| setUrl | React.Dispatch<React.SetStateAction<string | null>> | Function to update the URL state | 
| setLink | () => void | Function to apply the link in the editor | 
| removeLink | () => void | Function to remove the link from the editor | 
| label | string | Accessible label text for the button | 
| Icon | React.FC | Icon component for the link button | 
useLinkHandler() 
 A focused hook for handling link operations without UI state management.
Usage
function MyCustomLinkInterface() {  const { url, setUrl, setLink, removeLink } = useLinkHandler({  editor: myEditor,  onSetLink: () => console.log('Link applied!'),  })   return (  <div>  <input  value={url}  onChange={(e) => setUrl(e.target.value)}  onKeyDown={(e) => e.key === 'Enter' && setLink()}  />  <button onClick={setLink}>Apply</button>  <button onClick={removeLink}>Remove</button>  </div>  ) }Props
| Name | Type | Default | Description | 
|---|---|---|---|
| editor | Editor | null | undefined | The Tiptap editor instance | 
| onSetLink | () => void | undefined | Callback fired after setting a link | 
Return Values
| Name | Type | Description | 
|---|---|---|
| url | string | Current URL value for the link | 
| setUrl | React.Dispatch<React.SetStateAction<string | null>> | Function to update the URL state | 
| setLink | () => void | Function to apply the link in the editor | 
| removeLink | () => void | Function to remove the link from the editor | 
Utilities
canSetLink(editor) 
 Checks if a link can be set in the current editor state.
import { canSetLink } from '@/components/tiptap-ui/link-popover'  const canSet = canSetLink(editor) if (canSet) {  console.log('Link can be applied to current selection') }isLinkActive(editor) 
 Checks if a link is currently active in the editor.
import { isLinkActive } from '@/components/tiptap-ui/link-popover'  const isActive = isLinkActive(editor) if (isActive) {  console.log('A link is currently selected') }Keyboard Shortcuts
The link popover supports the following keyboard interactions:
- Enter: Apply the current URL as a link (when focused in the URL input)
- Escape: Close the popover (standard popover behavior)
Requirements
Dependencies
- @tiptap/react- Core Tiptap React integration
- @tiptap/extension-link- Link extension for link functionality
Referenced Components
- use-tiptap-editor(hook)
- use-mobile(hook)
- use-link-popover(hook)
- button(primitive)
- popover(primitive)
- card(primitive)
- input(primitive)
- separator(primitive)
- tiptap-utils(lib)
- corner-down-left-icon(icon)
- external-link-icon(icon)
- link-icon(icon)
- trash-icon(icon)