> ## Documentation Index
> Fetch the complete documentation index at: https://react.email/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# BubbleMenu

> Floating contextual toolbars for text, links, images, and buttons.

Everything is accessed through a single import:

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
import { BubbleMenu, bubbleMenuTriggers, useBubbleMenuContext } from '@react-email/editor/ui';
```

## Pre-built menus

Drop-in menus for common use cases. Each one handles its own trigger logic and plugin key.

### BubbleMenu.LinkDefault

Link editing menu that appears when clicking a link.

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
<BubbleMenu.LinkDefault />
```

**excludeItems** `('edit-link' | 'open-link' | 'unlink')[]` (default: \[])

Actions to hide from the toolbar.

**placement** `'top' | 'bottom'` (default: 'top')

Position relative to the link.

**offset** `number`

Distance from the link in pixels.

**onHide** `() =\> void`

Called when the bubble menu is hidden.

**validateUrl** `(value: string) =\> string | null`

Custom URL validator. Return the valid URL string or `null`.

**onLinkApply** `(href: string) =\> void`

Called after a link is applied.

**onLinkRemove** `() =\> void`

Called after a link is removed.

### BubbleMenu.ButtonDefault

Button link editing menu that appears when clicking a button.

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
<BubbleMenu.ButtonDefault />
```

Same props as `LinkDefault` (except `excludeItems`).

### BubbleMenu.ImageDefault

Image editing menu that appears when clicking an image.

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
<BubbleMenu.ImageDefault />
```

**excludeItems** `('edit-link' | 'unlink')[]` (default: \[])

Actions to hide from the toolbar.

**placement** `'top' | 'bottom'` (default: 'top')

Position relative to the image.

**offset** `number`

Distance from the image in pixels.

**onHide** `() =\> void`

Called when the bubble menu is hidden.

**validateUrl** `(value: string) =\> string | null`

Custom URL validator. Return the valid URL string or `null`.

**onLinkApply** `(href: string) =\> void`

Called after a link is applied to the image.

**onLinkRemove** `() =\> void`

Called after a link is removed from the image.

***

## Combining menus

A typical email editor uses multiple bubble menus together. Use `hideWhenActiveNodes` and `hideWhenActiveMarks`
to prevent overlapping menus:

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
import { BubbleMenu } from '@react-email/editor/ui';

<EditorProvider extensions={extensions} content={content}>
  <BubbleMenu
    hideWhenActiveNodes={['image', 'button']}
    hideWhenActiveMarks={['link']}
  />
  <BubbleMenu.LinkDefault />
  <BubbleMenu.ButtonDefault />
  <BubbleMenu.ImageDefault />
</EditorProvider>
```

***

## Compound components

Build fully custom menus using the compound API.

### BubbleMenu

Base container for all custom bubble menus. Provides editor context to children.
When rendered without children, it automatically renders the default text formatting toolbar.

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
import { BubbleMenu, bubbleMenuTriggers } from '@react-email/editor/ui';
import { PluginKey } from '@tiptap/pm/state';

const myPluginKey = new PluginKey('myCustomMenu');

<BubbleMenu
  trigger={bubbleMenuTriggers.node('image')}
  pluginKey={myPluginKey}
  placement="top"
>
  {/* your custom menu content */}
</BubbleMenu>
```

**trigger** `TriggerFn`

Controls when the menu is visible. Defaults to showing on text selection.
Use `bubbleMenuTriggers` for common patterns.

**pluginKey** `PluginKey`

Unique key for the ProseMirror plugin backing this menu. Required when
rendering multiple `BubbleMenu` instances to avoid collisions.
Import `PluginKey` from `@tiptap/pm/state`.

**hideWhenActiveNodes** `string[]` (default: \[])

Node types that prevent the menu from showing.

**hideWhenActiveMarks** `string[]` (default: \[])

Mark types that prevent the menu from showing.

**placement** `'top' | 'bottom'` (default: 'bottom')

Position relative to the selection.

**offset** `number` (default: 8)

Distance from the selection in pixels.

**onHide** `() =\> void`

Called when the bubble menu is hidden.

### bubbleMenuTriggers

Factory for common `trigger` functions:

| Trigger | Description |
| - | - |
| `bubbleMenuTriggers.textSelection(hideNodes?, hideMarks?)` | Show on text selection. This is the default. |
| `bubbleMenuTriggers.node(name)` | Show when a specific node type is active (e.g., `'button'`, `'image'`) |
| `bubbleMenuTriggers.nodeWithoutSelection(name)` | Show when a node is active but no text is selected (e.g., `'link'`) |

### Text formatting items

| Component | Description |
| - | - |
| `BubbleMenu.ItemGroup` | Visual grouping of items |
| `BubbleMenu.Separator` | Divider between groups |
| `BubbleMenu.Bold` | Bold toggle |
| `BubbleMenu.Italic` | Italic toggle |
| `BubbleMenu.Underline` | Underline toggle |
| `BubbleMenu.Strike` | Strikethrough toggle |
| `BubbleMenu.Code` | Inline code toggle |
| `BubbleMenu.Uppercase` | Uppercase toggle |
| `BubbleMenu.AlignLeft` | Left alignment |
| `BubbleMenu.AlignCenter` | Center alignment |
| `BubbleMenu.AlignRight` | Right alignment |
| `BubbleMenu.NodeSelector` | Block type dropdown |
| `BubbleMenu.LinkSelector` | Link add/edit popover |

### Link components

| Component | Description |
| - | - |
| `BubbleMenu.LinkToolbar` | Wrapper -- hides when editing mode is active |
| `BubbleMenu.LinkEditLink` | Button that enters editing mode |
| `BubbleMenu.LinkUnlink` | Removes the link |
| `BubbleMenu.LinkOpenLink` | Opens the link in a new tab |
| `BubbleMenu.LinkForm` | Inline form for editing link URLs |

### Button components

| Component | Description |
| - | - |
| `BubbleMenu.ButtonToolbar` | Wrapper -- hides when editing mode is active |
| `BubbleMenu.ButtonEditLink` | Button that enters editing mode |
| `BubbleMenu.ButtonUnlink` | Removes the button link |
| `BubbleMenu.ButtonForm` | Inline form for editing button URLs |

### Image components

| Component | Description |
| - | - |
| `BubbleMenu.ImageToolbar` | Wrapper -- hides when editing mode is active |
| `BubbleMenu.ImageEditLink` | Button that enters editing mode |
| `BubbleMenu.ImageUnlink` | Removes the link wrapping the image |
| `BubbleMenu.ImageForm` | Inline form for editing the image's link |

***

## Custom menu example

Here's a complete custom link bubble menu built from compound components:

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
import { BubbleMenu, bubbleMenuTriggers, useBubbleMenuContext } from '@react-email/editor/ui';
import { PluginKey } from '@tiptap/pm/state';

const linkKey = new PluginKey('myLinkMenu');

function MyLinkMenu() {
  return (
    <BubbleMenu
      trigger={bubbleMenuTriggers.nodeWithoutSelection('link')}
      pluginKey={linkKey}
      placement="top"
    >
      <BubbleMenu.LinkToolbar>
        <BubbleMenu.LinkEditLink />
        <BubbleMenu.LinkOpenLink />
        <BubbleMenu.LinkUnlink />
      </BubbleMenu.LinkToolbar>
      <BubbleMenu.LinkForm />
    </BubbleMenu>
  );
}
```

The `LinkToolbar` automatically hides when `LinkEditLink` is clicked, and `LinkForm`
appears in its place. When the user submits or cancels, the toolbar reappears.

***

## Context

Use `useBubbleMenuContext()` inside any child of `BubbleMenu` to access the editor and editing state:

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
import { useBubbleMenuContext } from '@react-email/editor/ui';

function CustomToolbarItem() {
  const { editor, isEditing, setIsEditing } = useBubbleMenuContext();

  return (
    <button onClick={() => setIsEditing(true)}>
      Edit
    </button>
  );
}
```

| Field | Type | Description |
| - | - | - |
| `editor` | `Editor` | The TipTap editor instance |
| `isEditing` | `boolean` | Whether the menu is in editing mode |
| `setIsEditing` | `(value: boolean) => void` | Toggle editing mode |

***

## CSS import

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
import '@react-email/editor/styles/bubble-menu.css';
```

> [!TIP]
>
> `@react-email/editor/themes/default.css` bundles all UI component styles.
> Unless you need to cherry-pick, use this single import:
>
> ```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
> import '@react-email/editor/themes/default.css';
> ```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.