List Dropdown Menu

Available for free

A fully accessible list dropdown menu for Tiptap editors. Easily toggle between different list types (bullet, ordered, task) with keyboard shortcut support and flexible customization options.

Installation

Add the component via the Tiptap CLI:

npx @tiptap/cli@latest add list-dropdown-menu

Components

<ListDropdownMenu />

A prebuilt React component that provides a dropdown menu for selecting different list types.

Usage

import { EditorContent, EditorContext, useEditor } from '@tiptap/react'
import { StarterKit } from '@tiptap/starter-kit'
import { TaskList } from '@tiptap/extension-task-list'
import { TaskItem } from '@tiptap/extension-task-item'
import { ListDropdownMenu } from '@/components/tiptap-ui/list-dropdown-menu'

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

export default function MyEditor() {
  const editor = useEditor({
    immediatelyRender: false,
    extensions: [StarterKit, TaskList, TaskItem.configure({ nested: true })],
    content: `
        <ul>
            <li>
                <strong>Bold</strong> for emphasis with <code>**</code> or <code>⌘+B</code> or the <code>B</code> button.
            </li>
            <li>
                <em>Italic</em> for subtle nuances with <code>*</code> or <code>⌘+I</code> or the <code>I</code> button.
            </li>
            <li>
                <s>Strikethrough</s> to show revisions with <code>~~</code> or the <code>~~S~~</code> button.
            </li>
        </ul>
        <ul data-type="taskList">
            <li data-type="taskItem" data-checked="true">
                <div>
                    Test template
                </div>
            </li>
            <li data-type="taskItem" data-checked="false">
                <div>
                    <a target="_blank" rel="noopener noreferrer nofollow" href="https://tiptap.dev/pricing">Create account</a>
                </div>
            </li>
            <li data-type="taskItem" data-checked="false">
                <div>
                   Download free template
                </div>
            </li>
        </ul>
        `,
  })

  return (
    <EditorContext.Provider value={{ editor }}>
      <ListDropdownMenu
        editor={editor}
        types={['bulletList', 'orderedList', 'taskList']}
        hideWhenUnavailable={true}
        portal={false}
        onOpenChange={(isOpen) => console.log('Dropdown opened:', isOpen)}
      />

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

Props

NameTypeDefaultDescription
editorEditor | nullundefinedThe Tiptap editor instance
typesListType[]["bulletList", "orderedList", "taskList"]The list types to display in the dropdown
hideWhenUnavailablebooleanfalseHides the dropdown when no list types are available
onOpenChange(isOpen: boolean) => voidundefinedCallback fired when the dropdown opens or closes
portalbooleanfalseWhether to render the dropdown menu in a portal

Hooks

useListDropdownMenu()

A custom hook for managing list dropdown state and determining which options are available.

Usage

import { useListDropdownMenu } from '@/components/tiptap-ui/list-dropdown-menu'
import { ListButton } from '@/components/tiptap-ui/list-button'
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
} from '@/components/tiptap-ui-primitive/dropdown-menu'
import { ButtonGroup } from '@/components/tiptap-ui-primitive/button'

function MyListDropdown() {
  const { filteredLists, canToggle, isActive, isVisible, Icon, activeType } = useListDropdownMenu({
    editor,
    types: ['bulletList', 'orderedList', 'taskList'],
    hideWhenUnavailable: true,
  })

  if (!isVisible) return null

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <button disabled={!canToggle}>
          <Icon />
          Lists {isActive ? '(Active)' : ''}
        </button>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <ButtonGroup>
          {filteredLists.map((option) => (
            <DropdownMenuItem key={option.type} asChild>
              <ListButton editor={editor} type={option.type} text={option.label} />
            </DropdownMenuItem>
          ))}
        </ButtonGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

Props

NameTypeDefaultDescription
editorEditor | nullundefinedThe Tiptap editor instance
typesListType[]["bulletList", "orderedList", "taskList"]Array of list types to display in the dropdown
hideWhenUnavailablebooleanfalseWhether to hide when no lists can be toggled

Return Values

NameTypeDescription
isVisiblebooleanWhether the dropdown should be rendered
activeTypeListType | undefinedCurrently active list type
isActivebooleanIf any list type is currently active
canTogglebooleanIf any list type can be toggled
typesListType[]Array of list types from configuration
filteredListsListOption[]List options filtered by available types
labelstringAccessible label text for the dropdown
IconReact.FCIcon component for the active list or default

Utilities

canToggleAnyList(editor, listTypes)

Checks if any of the specified list types can be toggled in the current editor state.

import { canToggleAnyList } from '@/components/tiptap-ui/list-dropdown-menu'

const canToggle = canToggleAnyList(editor, ['bulletList', 'orderedList'])
if (canToggle) {
  console.log('At least one list type can be toggled')
}

Parameters

NameTypeDescription
editorEditor | nullThe Tiptap editor instance
listTypesListType[]Array of list types to check

Returns

boolean - Whether at least one list type can be toggled.

isAnyListActive(editor, listTypes)

Checks if any of the specified list types are currently active in the editor.

import { isAnyListActive } from '@/components/tiptap-ui/list-dropdown-menu'

const isActive = isAnyListActive(editor, ['bulletList', 'orderedList', 'taskList'])
if (isActive) {
  console.log('At least one list type is currently active')
}

Parameters

NameTypeDescription
editorEditor | nullThe Tiptap editor instance
listTypesListType[]Array of list types to check

Returns

boolean - Whether at least one list type is currently active.

getFilteredListOptions(availableTypes)

Filters the predefined list options based on the available types.

import { getFilteredListOptions } from '@/components/tiptap-ui/list-dropdown-menu'

const availableOptions = getFilteredListOptions(['bulletList', 'taskList'])
// Returns only bullet list and task list options

Parameters

NameTypeDescription
availableTypesListType[]Array of list types to include

Returns

ListOption[] - Filtered array of list options.

getActiveListType(editor, availableTypes)

Gets the currently active list type from the available types.

import { getActiveListType } from '@/components/tiptap-ui/list-dropdown-menu'

const activeType = getActiveListType(editor, ['bulletList', 'orderedList', 'taskList'])
console.log('Current list type:', activeType)

Parameters

NameTypeDescription
editorEditor | nullThe Tiptap editor instance
availableTypesListType[]Array of list types to check

Returns

ListType | undefined - The active list type, or undefined if no list is active.

Keyboard Shortcuts

Each list type supports keyboard shortcuts (inherited from the underlying ListButton components):

  • Cmd/Ctrl + Shift + 8: Toggle bullet list
  • Cmd/Ctrl + Shift + 7: Toggle ordered list
  • Cmd/Ctrl + Shift + 9: Toggle task list

The shortcuts are automatically registered when the list types are available in the editor schema.

Requirements

Dependencies

  • @tiptap/react - Core Tiptap React integration
  • @tiptap/starter-kit - Basic Tiptap extensions including list support
  • @tiptap/extension-task-list - Task list extension
  • @tiptap/extension-task-item - Task item extension

Referenced Components

  • use-tiptap-editor (hook)
  • list-button (component)
  • button (primitive)
  • dropdown-menu (primitive)
  • card (primitive)
  • tiptap-utils (lib)
  • chevron-down-icon (icon)
  • list-icon (icon)
  • list-ordered-icon (icon)
  • list-todo-icon (icon)