Skip to main content

Quick start

Add BubbleMenu.Default 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.Default />
    </EditorProvider>
  );
}
Select text to see the toolbar with formatting, alignment, node type selection, and link controls.

Excluding items

Hide specific items from the default bubble menu using excludeItems:
<BubbleMenu.Default excludeItems={['strike', 'code', 'uppercase']} />
All excludable item keys:
KeyDescription
boldBold toggle
italicItalic toggle
underlineUnderline toggle
strikeStrikethrough toggle
codeInline code toggle
uppercaseUppercase toggle
align-leftLeft alignment
align-centerCenter alignment
align-rightRight alignment
node-selectorBlock type selector (paragraph, headings, etc.)
link-selectorLink add/edit control

Hiding on specific nodes or marks

Prevent the bubble menu from appearing on certain node types or when certain marks are active:
<BubbleMenu.Default
  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.Root:
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.Default hideWhenActiveNodes={['button']} hideWhenActiveMarks={['link']} />

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

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.Root>
        <BubbleMenu.ItemGroup>
          <BubbleMenu.Bold />
          <BubbleMenu.Italic />
          <BubbleMenu.Underline />
        </BubbleMenu.ItemGroup>
        <BubbleMenu.ItemGroup>
          <BubbleMenu.AlignLeft />
          <BubbleMenu.AlignCenter />
          <BubbleMenu.AlignRight />
        </BubbleMenu.ItemGroup>
      </BubbleMenu.Root>
    </EditorProvider>
  );
}
BubbleMenu.Root 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.Root placement="top" offset={12}>
  {/* items */}
</BubbleMenu.Root>
placement
'top' | 'bottom'
default:"'bottom'"
Whether the menu appears above or below the selection.
offset
number
default:"8"
Distance from the selection in pixels.