Skip to main content

Quick start

Add BubbleMenu as a child of EditorProvider to get a fully-featured formatting toolbar.
import { StarterKit } from '@react-email/editor/extensions';
import { BubbleMenu } from '@react-email/editor/ui';
import { EditorProvider } from '@tiptap/react';
import '@react-email/editor/themes/default.css';

const extensions = [StarterKit];

export function MyEditor() {
  return (
    <EditorProvider extensions={extensions} content={content}>
      <BubbleMenu />
    </EditorProvider>
  );
}
Select text to see the toolbar with formatting, alignment, node type selection, and link controls. When BubbleMenu has no children, it renders the default text formatting toolbar automatically.
Even though you can compose BubbleMenu from primitives and style it that way, the default can also be styled using
CSS variables and data-re-* selectors.

Hiding on specific nodes or marks

Prevent the bubble menu from appearing on certain node types or when certain marks are active.
<BubbleMenu
  hideWhenActiveNodes={['codeBlock', 'button']}
  hideWhenActiveMarks={['link']}
/>
This is useful when combining the text bubble menu with contextual menus for links, images, or buttons — each gets its own menu via BubbleMenu.
import { BubbleMenu, bubbleMenuTriggers } from '@react-email/editor/ui';
import { PluginKey } from '@tiptap/pm/state';

const linkPluginKey = new PluginKey('linkBubbleMenu');

<>
  {/* Hide text bubble menu on links and buttons -- their own menus handle those */}
  <BubbleMenu hideWhenActiveNodes={['button']} hideWhenActiveMarks={['link']} />

  <BubbleMenu
    trigger={bubbleMenuTriggers.nodeWithoutSelection('link')}
    pluginKey={linkPluginKey}
  >
    <BubbleMenu.LinkToolbar>
      <BubbleMenu.LinkEditLink />
      <BubbleMenu.LinkOpenLink />
      <BubbleMenu.LinkUnlink />
    </BubbleMenu.LinkToolbar>
  </BubbleMenu>
</>

Composing from primitives

For full control, build a custom bubble menu using the compound component API.
import { StarterKit } from '@react-email/editor/extensions';
import { BubbleMenu } from '@react-email/editor/ui';
import { EditorProvider } from '@tiptap/react';

export function MyEditor() {
  return (
    <EditorProvider extensions={[StarterKit]} content={content}>
      <BubbleMenu>
        <BubbleMenu.ItemGroup>
          <BubbleMenu.Bold />
          <BubbleMenu.Italic />
          <BubbleMenu.Underline />
        </BubbleMenu.ItemGroup>
        <BubbleMenu.ItemGroup>
          <BubbleMenu.AlignLeft />
          <BubbleMenu.AlignCenter />
          <BubbleMenu.AlignRight />
        </BubbleMenu.ItemGroup>
      </BubbleMenu>
    </EditorProvider>
  );
}
BubbleMenu wraps everything, BubbleMenu.ItemGroup creates visual groupings, and individual items render the toggle buttons.

Available items

ComponentDescription
BubbleMenu.BoldBold toggle
BubbleMenu.ItalicItalic toggle
BubbleMenu.UnderlineUnderline toggle
BubbleMenu.StrikeStrikethrough toggle
BubbleMenu.CodeInline code toggle
BubbleMenu.UppercaseUppercase toggle
BubbleMenu.AlignLeftLeft alignment
BubbleMenu.AlignCenterCenter alignment
BubbleMenu.AlignRightRight alignment
BubbleMenu.NodeSelectorBlock type dropdown (paragraph, h1-h3, etc.)
BubbleMenu.LinkSelectorLink add/edit popover
BubbleMenu.SeparatorVisual separator between groups

Placement and offset

Control where the bubble menu appears relative to the selection.
<BubbleMenu placement="top" offset={12}>
  {/* items */}
</BubbleMenu>
placement
'top' | 'bottom'
default:"'bottom'"
Whether the menu appears above or below the selection.
offset
number
default:"8"
Distance from the selection in pixels.

Examples

See bubble menus in action with runnable examples:

Bubble Menu

Default bubble menu with text selection.

Custom Bubble Menu

Composing a custom menu from primitives.