> ## 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 />
```

<ResponseField name="excludeItems" type="('edit-link' | 'open-link' | 'unlink')[]" default="[]">
  Actions to hide from the toolbar.
</ResponseField>

<ResponseField name="placement" type="'top' | 'bottom'" default="'top'">
  Position relative to the link.
</ResponseField>

<ResponseField name="offset" type="number">
  Distance from the link in pixels.
</ResponseField>

<ResponseField name="onHide" type="() => void">
  Called when the bubble menu is hidden.
</ResponseField>

<ResponseField name="validateUrl" type="(value: string) => string | null">
  Custom URL validator. Return the valid URL string or `null`.
</ResponseField>

<ResponseField name="onLinkApply" type="(href: string) => void">
  Called after a link is applied.
</ResponseField>

<ResponseField name="onLinkRemove" type="() => void">
  Called after a link is removed.
</ResponseField>

### 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 />
```

<ResponseField name="excludeItems" type="('edit-link' | 'unlink')[]" default="[]">
  Actions to hide from the toolbar.
</ResponseField>

<ResponseField name="placement" type="'top' | 'bottom'" default="'top'">
  Position relative to the image.
</ResponseField>

<ResponseField name="offset" type="number">
  Distance from the image in pixels.
</ResponseField>

<ResponseField name="onHide" type="() => void">
  Called when the bubble menu is hidden.
</ResponseField>

<ResponseField name="validateUrl" type="(value: string) => string | null">
  Custom URL validator. Return the valid URL string or `null`.
</ResponseField>

<ResponseField name="onLinkApply" type="(href: string) => void">
  Called after a link is applied to the image.
</ResponseField>

<ResponseField name="onLinkRemove" type="() => void">
  Called after a link is removed from the image.
</ResponseField>

***

## 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>
```

<ResponseField name="trigger" type="TriggerFn">
  Controls when the menu is visible. Defaults to showing on text selection.
  Use `bubbleMenuTriggers` for common patterns.
</ResponseField>

<ResponseField name="pluginKey" type="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`.
</ResponseField>

<ResponseField name="hideWhenActiveNodes" type="string[]" default="[]">
  Node types that prevent the menu from showing.
</ResponseField>

<ResponseField name="hideWhenActiveMarks" type="string[]" default="[]">
  Mark types that prevent the menu from showing.
</ResponseField>

<ResponseField name="placement" type="'top' | 'bottom'" default="'bottom'">
  Position relative to the selection.
</ResponseField>

<ResponseField name="offset" type="number" default="8">
  Distance from the selection in pixels.
</ResponseField>

<ResponseField name="onHide" type="() => void">
  Called when the bubble menu is hidden.
</ResponseField>

### 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';
  ```
</Tip>
