Find out what's new in Tiptap Editor 3.0

Turn Into Dropdown

Available in Start plan

A fully accessible block transformation dropdown for Tiptap editors. Convert between different content types like headings, paragraphs, lists, blockquotes, and code blocks with an intuitive dropdown interface.

Installation

Add the component via the Tiptap CLI:

npx @tiptap/cli@latest add turn-into-dropdown

Components

<TurnIntoDropdown />

A comprehensive dropdown component for transforming block types in a Tiptap editor.

Usage

import { EditorContent, EditorContext, useEditor } from '@tiptap/react'
import { StarterKit } from '@tiptap/starter-kit'
import { TurnIntoDropdown } from '@/components/tiptap-ui/turn-into-dropdown'

import '@/components/tiptap-node/paragraph-node/paragraph-node.scss'
import '@/components/tiptap-node/heading-node/heading-node.scss'
import '@/components/tiptap-node/list-node/list-node.scss'
import '@/components/tiptap-node/blockquote-node/blockquote-node.scss'
import '@/components/tiptap-node/code-block-node/code-block-node.scss'

export default function MyEditor() {
  const editor = useEditor({
    immediatelyRender: false,
    extensions: [StarterKit],
    content: `
      <h1>Document Title</h1>
      <p>Welcome to the rich text editor. You can transform this paragraph into different block types.</p>
      <ul>
        <li>Transform any block element</li>
        <li>Choose from multiple content types</li>
        <li>Maintain content while changing structure</li>
      </ul>
    `,
  })

  return (
    <EditorContext.Provider value={{ editor }}>
      <div className="toolbar">
        <TurnIntoDropdown
          editor={editor}
          hideWhenUnavailable={false}
          blockTypes={['paragraph', 'heading', 'bulletList', 'orderedList', 'blockquote']}
          portal={false}
          useCardLayout={true}
          onOpenChange={(isOpen) => console.log('Dropdown toggled:', isOpen)}
        />
      </div>

      <EditorContent editor={editor} role="presentation" />
    </EditorContext.Provider>
  )
}

Props

NameTypeDefaultDescription
editorEditor | nullundefinedThe Tiptap editor instance
hideWhenUnavailablebooleanfalseHides dropdown when block transformation unavailable
blockTypesstring[]All supported typesWhich block types to show in the dropdown
portalbooleanfalseWhether to render dropdown menu in a portal
useCardLayoutbooleantrueWhether to use card layout for dropdown content
onOpenChange(isOpen: boolean) => voidundefinedCallback fired when dropdown state changes

<TurnIntoDropdownContent />

The dropdown content component that renders the available block type options.

Usage

import { TurnIntoDropdownContent } from '@/components/tiptap-ui/turn-into-dropdown'

function CustomDropdownContent() {
  return (
    <TurnIntoDropdownContent
      blockTypes={['paragraph', 'heading', 'bulletList']}
      useCardLayout={false}
    />
  )
}

Props

NameTypeDefaultDescription
blockTypesstring[]AllWhich block types to show
useCardLayoutbooleantrueWhether to wrap content in a card layout

Hooks

useTurnIntoDropdown()

A custom hook to build your own turn into dropdown with full control over rendering and behavior.

Usage

import { useTurnIntoDropdown } from '@/components/tiptap-ui/turn-into-dropdown'

function MyTurnIntoDropdown() {
  const {
    isVisible,
    canToggle,
    isOpen,
    activeBlockType,
    handleOpenChange,
    filteredOptions,
    label,
    Icon,
  } = useTurnIntoDropdown({
    editor: myEditor,
    hideWhenUnavailable: true,
    blockTypes: ['paragraph', 'heading', 'bulletList'],
    onOpenChange: (isOpen) => console.log('Dropdown toggled:', isOpen),
  })

  if (!isVisible) return null

  return (
    <DropdownMenu open={isOpen} onOpenChange={handleOpenChange}>
      <DropdownMenuTrigger asChild>
        <button disabled={!canToggle} aria-label={label}>
          <span>{activeBlockType?.label || 'Text'}</span>
          <Icon />
        </button>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        {filteredOptions.map((option) => (
          <DropdownMenuItem key={option.type}>{option.label}</DropdownMenuItem>
        ))}
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

Props

NameTypeDefaultDescription
editorEditor | nullundefinedThe Tiptap editor instance
hideWhenUnavailablebooleanfalseHides dropdown when block transformation unavailable
blockTypesstring[]All typesWhich block types to show in the dropdown
onOpenChange(isOpen: boolean) => voidundefinedCallback fired when dropdown state changes

Return Values

NameTypeDescription
isVisiblebooleanWhether the dropdown should be rendered
canTogglebooleanIf block transformation is currently allowed
isOpenbooleanWhether the dropdown is currently open
setIsOpen(open: boolean) => voidFunction to set dropdown open state
activeBlockTypeBlockTypeOptionCurrently active block type information
handleOpenChange(open: boolean) => voidFunction to handle dropdown open/close
filteredOptionsBlockTypeOption[]Filtered list of available block type options
labelstringAccessible label text for the dropdown
IconReact.FCIcon component for the dropdown trigger

Block Types

The Turn Into Dropdown supports transformation between various block types:

Supported Block Types

  • paragraph: Regular text paragraphs
  • heading: Headings (levels 1, 2, and 3)
  • bulletList: Bulleted (unordered) lists
  • orderedList: Numbered (ordered) lists
  • taskList: To-do lists with checkboxes
  • blockquote: Quote blocks for highlighted text
  • codeBlock: Code blocks with syntax highlighting

Block Type Options

Each block type includes:

interface BlockTypeOption {
  type: string // The node type name
  label: string // Display label
  level?: number // For headings (1, 2, 3)
  isActive: (editor: Editor) => boolean // Function to check if active
}

Utilities

canTurnInto(editor, allowedBlockTypes?)

Checks if block transformation can be performed in the current editor state.

import { canTurnInto } from '@/components/tiptap-ui/turn-into-dropdown'

const canTransform = canTurnInto(editor, ['paragraph', 'heading'])
if (canTransform) {
  console.log('Block transformation is available')
}

Parameters

NameTypeDescription
editorEditor | nullThe Tiptap editor instance
allowedBlockTypesstring[]Optional array of allowed block types

Returns

boolean - Whether block transformation can be performed.

getFilteredBlockTypeOptions(blockTypes?)

Gets filtered block type options based on available types.

import { getFilteredBlockTypeOptions } from '@/components/tiptap-ui/turn-into-dropdown'

const options = getFilteredBlockTypeOptions(['paragraph', 'heading', 'bulletList'])
console.log(
  'Available options:',
  options.map((o) => o.label),
)

Parameters

NameTypeDescription
blockTypesstring[]Optional array of block types to filter

Returns

BlockTypeOption[] - Filtered array of block type options.

getActiveBlockType(editor, blockTypes?)

Gets the currently active block type from the available options.

import { getActiveBlockType } from '@/components/tiptap-ui/turn-into-dropdown'

const activeType = getActiveBlockType(editor, ['paragraph', 'heading'])
console.log('Current block type:', activeType?.label)

Parameters

NameTypeDescription
editorEditor | nullThe Tiptap editor instance
blockTypesstring[]Optional array of allowed block types

Returns

BlockTypeOption - Currently active block type option.

shouldShowTurnInto(params)

Determines if the turn into dropdown should be visible based on editor state.

import { shouldShowTurnInto } from '@/components/tiptap-ui/turn-into-dropdown'

const shouldShow = shouldShowTurnInto({
  editor: myEditor,
  hideWhenUnavailable: true,
  blockTypes: ['paragraph', 'heading'],
})

Parameters

NameTypeDescription
params.editorEditor | nullThe Tiptap editor instance
params.hideWhenUnavailablebooleanWhether to hide when transformation unavailable
params.blockTypesstring[]Optional array of allowed block types

Returns

boolean - Whether the dropdown should be shown.

Behavior and Constraints

Selection Requirements

The Turn Into Dropdown works with different selection types:

  • Text Selection: Works within block elements
  • Node Selection: Works when entire blocks are selected
  • Empty Selection: Works when cursor is placed within a block

Supported Transformations

The component intelligently handles transformations between different block types:

  • Content Preservation: Text content is maintained during transformation
  • Structure Changes: Block structure changes while preserving inline formatting
  • List Handling: Supports conversion between different list types
  • Heading Levels: Allows selection of different heading levels

Editor State Dependencies

  • Editable Editor: Only works when the editor is in editable mode
  • Valid Block Context: Requires cursor to be within a transformable block
  • Extension Availability: Requires relevant extensions (StarterKit covers most cases)

Integration Examples

With Custom Block Types

function EditorWithCustomBlocks() {
  const customBlockTypes = ['paragraph', 'heading', 'bulletList', 'blockquote']

  return <TurnIntoDropdown blockTypes={customBlockTypes} hideWhenUnavailable={true} />
}

With Portal Rendering

function FloatingTurnIntoDropdown() {
  return (
    <TurnIntoDropdown
      portal={true} // Renders in a portal for better z-index control
      useCardLayout={false} // Simplified layout
      hideWhenUnavailable={true}
    />
  )
}

With State Tracking

function EditorWithStateTracking() {
  const [currentBlockType, setCurrentBlockType] = useState<string>('paragraph')

  const handleOpenChange = (isOpen: boolean) => {
    if (!isOpen) {
      // Update tracking when dropdown closes
      const activeType = getActiveBlockType(editor)
      setCurrentBlockType(activeType?.type || 'paragraph')
    }
  }

  return (
    <div>
      <p>Current block type: {currentBlockType}</p>
      <TurnIntoDropdown onOpenChange={handleOpenChange} />
    </div>
  )
}

Custom Dropdown Implementation

function CustomTurnIntoDropdown() {
  const { isVisible, canToggle, activeBlockType, filteredOptions, handleOpenChange } =
    useTurnIntoDropdown({
      hideWhenUnavailable: true,
      blockTypes: ['paragraph', 'heading', 'bulletList', 'orderedList'],
    })

  if (!isVisible) return null

  return (
    <div className="custom-turn-into">
      <select
        disabled={!canToggle}
        value={activeBlockType?.type || 'paragraph'}
        onChange={(e) => {
          const option = filteredOptions.find((opt) => opt.type === e.target.value)
          if (option?.onClick) {
            option.onClick()
          }
        }}
      >
        {filteredOptions.map((option) => (
          <option key={option.type} value={option.type}>
            {option.label}
          </option>
        ))}
      </select>
    </div>
  )
}

Requirements

Dependencies

  • @tiptap/react - Core Tiptap React integration

Extensions

Any extensions that provide the block types you want to support:

  • @tiptap/starter-kit - Provides most common block types
  • Individual extensions for specific block types

Referenced Components

  • use-tiptap-editor (hook)
  • text-button, heading-button, list-button, blockquote-button, code-block-button (ui components)
  • button, button-group (primitives)
  • dropdown-menu (primitive)
  • card (primitive)
  • chevron-down-icon (icon)