Find out what's new in Tiptap Editor 3.0

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-popover

Components

<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

NameTypeDefaultDescription
editorEditor | nullundefinedThe Tiptap editor instance
hideWhenUnavailablebooleanfalseHides the button when link functionality is not available
onSetLink() => voidundefinedCallback fired after a link is successfully set
onOpenChange(isOpen: boolean) => voidundefinedCallback fired when the popover opens or closes
autoOpenOnLinkActivebooleantrueWhether 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

NameTypeDefaultDescription
editorEditor | nullundefinedThe Tiptap editor instance
hideWhenUnavailablebooleanfalseHides functionality if link cannot be applied
onSetLink() => voidundefinedCallback fired after setting a link

Return Values

NameTypeDescription
isVisiblebooleanWhether the link functionality should be rendered
canSetbooleanIf a link can be set in the current context
isActivebooleanIf a link is currently active/selected
urlstringCurrent URL value for the link
setUrlReact.Dispatch<React.SetStateAction<string | null>>Function to update the URL state
setLink() => voidFunction to apply the link in the editor
removeLink() => voidFunction to remove the link from the editor
labelstringAccessible label text for the button
IconReact.FCIcon 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

NameTypeDefaultDescription
editorEditor | nullundefinedThe Tiptap editor instance
onSetLink() => voidundefinedCallback fired after setting a link

Return Values

NameTypeDescription
urlstringCurrent URL value for the link
setUrlReact.Dispatch<React.SetStateAction<string | null>>Function to update the URL state
setLink() => voidFunction to apply the link in the editor
removeLink() => voidFunction 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 '@/registry/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 '@/registry/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)