HeroUI Pro

Rich Text Editor

A composable Tiptap editor with toolbar controls, selection bubble menu, JSON value updates, and character count support.

Usage

Anatomy

Import the RichTextEditor component and arrange its parts with dot notation.

import {RichTextEditor} from "@heroui-pro/react";

<RichTextEditor defaultValue={documentJson} onValueChange={setDocumentJson}>
  <RichTextEditor.Shell>
    <RichTextEditor.Toolbar>
      <RichTextEditor.ToolbarGroup>
        <RichTextEditor.ToggleButton command="bold" />
        <RichTextEditor.ToggleButton command="italic" />
        <RichTextEditor.LinkPopover>
          <RichTextEditor.LinkPopover.Trigger />
          <RichTextEditor.LinkPopover.Content>
            <RichTextEditor.LinkPopover.Input />
            <RichTextEditor.LinkPopover.Actions>
              <RichTextEditor.LinkPopover.UnsetButton />
              <RichTextEditor.LinkPopover.ApplyButton />
            </RichTextEditor.LinkPopover.Actions>
          </RichTextEditor.LinkPopover.Content>
        </RichTextEditor.LinkPopover>
      </RichTextEditor.ToolbarGroup>
    </RichTextEditor.Toolbar>
    <RichTextEditor.Content />
    <RichTextEditor.BubbleMenu>
      <RichTextEditor.ToggleButton command="bold" />
    </RichTextEditor.BubbleMenu>
    <RichTextEditor.Footer>
      <RichTextEditor.CharacterCount />
    </RichTextEditor.Footer>
  </RichTextEditor.Shell>
</RichTextEditor>;

Controlled

Character Count

Placeholder

Disabled And Read Only

Custom Composition

Extensible Commands

Use CommandButton, FloatingMenu, SuggestionMenu, and the editor hooks to connect custom Tiptap commands without waiting for a new built-in prop.

import {RichTextEditor, filterRichTextEditorSuggestionItems} from "@heroui-pro/react";

const slashItems = ({query}) =>
  filterRichTextEditorSuggestionItems(
    [
      {
        title: "Heading 1",
        keywords: ["title", "h1"],
        command: ({editor, range}) => {
          editor.chain().focus().deleteRange(range).setNode("heading", {level: 1}).run();
        },
      },
    ],
    query,
  );

<RichTextEditor extensions={customExtensions}>
  <RichTextEditor.Shell>
    <RichTextEditor.Toolbar>
      <RichTextEditor.CommandButton
        aria-label="Insert date"
        onCommand={(editor) => editor.chain().focus().insertContent("27 May 2026").run()}
      />
    </RichTextEditor.Toolbar>
    <RichTextEditor.Content />
    <RichTextEditor.FloatingMenu>
      <RichTextEditor.CommandButton
        aria-label="Add heading"
        onCommand={(editor) => editor.chain().focus().toggleHeading({level: 2}).run()}
      />
    </RichTextEditor.FloatingMenu>
    <RichTextEditor.SuggestionMenu char="/" items={slashItems} />
  </RichTextEditor.Shell>
</RichTextEditor>;

CSS Classes

Base Classes

  • .rich-text-editor - Root wrapper
  • .rich-text-editor__shell - Editor surface

Element Classes

  • .rich-text-editor__toolbar - Toolbar container
  • .rich-text-editor__toolbar-group - Toolbar group
  • .rich-text-editor__toolbar-button - Toolbar button slot
  • .rich-text-editor__toolbar-separator - Toolbar separator
  • .rich-text-editor__content - Tiptap content host
  • .rich-text-editor__prosemirror - ProseMirror editable element
  • .rich-text-editor__bubble-menu - Floating selection toolbar
  • .rich-text-editor__bubble-menu-toolbar - Toolbar inside the floating selection menu
  • .rich-text-editor__floating-menu - Floating empty-line toolbar
  • .rich-text-editor__floating-menu-toolbar - Toolbar inside the floating empty-line menu
  • .rich-text-editor__suggestion-menu - Triggered suggestion menu
  • .rich-text-editor__suggestion-menu-item - Suggestion menu item
  • .rich-text-editor__footer - Footer row
  • .rich-text-editor__character-count - Character and word count
  • .rich-text-editor__link-popover - Link popover surface
  • .rich-text-editor__link-input - Link URL input

States

  • Disabled: [data-disabled="true"] on .rich-text-editor
  • Read only: [data-readonly="true"] on .rich-text-editor
  • Over limit: [data-over-limit="true"] on .rich-text-editor__character-count
  • Active command: [data-active="true"] on toolbar buttons

API Reference

RichTextEditor

The root provider. It owns the Tiptap editor instance and emits JSON-first value updates.

PropTypeDefaultDescription
valueJSONContent-Controlled Tiptap JSON document
defaultValueJSONContent-Initial uncontrolled JSON document
onValueChange(value, details) => void-Called with JSON plus HTML, text, empty state, character count, and word count
placeholderstring"Start writing..."Placeholder text for empty content
maxLengthnumber-CharacterCount limit
isDisabledbooleanfalseDisables editing and controls
isReadOnlybooleanfalseKeeps content focusable but not editable
extensionsExtensions-Additional Tiptap extensions appended after defaults
editorOptionsPartial<EditorOptions>-Advanced Tiptap editor options

RichTextEditor.ToggleButton

Runs a formatting command and subscribes to active/disabled editor state.

PropTypeDescription
command"bold" | "italic" | "underline" | "strike" | "code" | "blockquote" | "bulletList" | "orderedList" | "codeBlock" | "heading-1" | "heading-2" | "heading-3"Formatting command
tooltipReactNodeOptional tooltip content

RichTextEditor.ActionButton

Runs editor actions.

PropTypeDescription
action"undo" | "redo" | "clearFormatting" | "clearContent"Editor action
tooltipReactNodeOptional tooltip content

RichTextEditor.CommandButton

Runs any custom Tiptap command.

PropTypeDescription
onCommand(editor) => void | booleanCommand to run with the current editor
isActiveboolean | (editor) => booleanActive state used for styling and aria-pressed
isDisabledboolean | (editor) => booleanCommand-specific disabled state
tooltipReactNodeOptional tooltip content

RichTextEditor.LinkPopover

Compound popover for applying and removing links through Tiptap link commands.

Subcomponents: Trigger, Content, Input, Actions, ApplyButton, and UnsetButton.

RichTextEditor.Content

Renders the Tiptap EditorContent for the current editor instance.

RichTextEditor.BubbleMenu

Wraps Tiptap's React BubbleMenu and renders its children inside a HeroUI Toolbar. The default visibility shows the menu for non-empty text selections while the editor is focused.

PropTypeDefaultDescription
toolbarPropsToolbarProps-Props forwarded to the internal selection toolbar

RichTextEditor.FloatingMenu

Wraps Tiptap's React FloatingMenu and renders its children inside a HeroUI Toolbar. By default, Tiptap shows this menu on an empty editable text block.

PropTypeDefaultDescription
toolbarPropsToolbarProps-Props forwarded to the internal insertion toolbar

RichTextEditor.SuggestionMenu

Registers a Tiptap suggestion plugin for slash commands, mentions, variables, snippets, and other triggered menus. If no render function is provided, items with {title, description, icon, command} render in the default menu.

PropTypeDefaultDescription
charstring"/"Trigger character
items({query, editor}) => Item[] | Promise<Item[]>-Suggestion item loader
children(props) => ReactNode-Custom renderer for complete menu control
onSelect(props) => void-Selection handler for custom item shapes
pluginKeystring | PluginKey-Stable ProseMirror plugin key
allowSpacesbooleanfalseAllows spaces in the query
allowedPrefixesstring[] | null[" "]Prefixes allowed before the trigger
startOfLinebooleanfalseRestricts matches to the start of a line
maxHeightnumber384Max menu height in pixels

Hooks

HookDescription
useRichTextEditor()Returns {editor, isDisabled, isReadOnly, maxLength} from the nearest editor
useRichTextEditorState(selector, equalityFn)Subscribes to selected Tiptap editor state without re-rendering for unrelated transactions

RichTextEditor.CharacterCount

Displays the CharacterCount extension storage values.

PropTypeDefaultDescription
showWordsbooleanfalseAppends word count to the default label
childrenReactNode | (stats) => ReactNode-Custom count rendering

On this page