> ## Documentation Index
> Fetch the complete documentation index at: https://vuemail.dev/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:

```ts theme={null}
import { BubbleMenu, bubbleMenuTriggers, useBubbleMenuContext } from '@vuemaildev/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.

```vue theme={null}
<BubbleMenu.LinkDefault />
```

**exclude-items** `('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.

**validate-url** `(value: string) =\> string | null`

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

**@hide** `() =\> void`

Emitted when the bubble menu is hidden.

**@link-apply** `(href: string) =\> void`

Emitted after a link is applied.

**@link-remove** `() =\> void`

Emitted after a link is removed.

### BubbleMenu.ButtonDefault

Button link editing menu that appears when clicking a button.

```vue theme={null}
<BubbleMenu.ButtonDefault />
```

Same props and events as `LinkDefault` (except `exclude-items`).

### BubbleMenu.ImageDefault

Image editing menu that appears when clicking an image.

```vue theme={null}
<BubbleMenu.ImageDefault />
```

**exclude-items** `('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.

**validate-url** `(value: string) =\> string | null`

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

**@hide** `() =\> void`

Emitted when the bubble menu is hidden.

**@link-apply** `(href: string) =\> void`

Emitted after a link is applied to the image.

**@link-remove** `() =\> void`

Emitted after a link is removed from the image.

***

## Combining menus

A typical email editor uses multiple bubble menus together. Use `hide-when-active-nodes` and `hide-when-active-marks`
to prevent overlapping menus:

```vue theme={null}
<EditorProvider :extensions="extensions" :content="content">
  <BubbleMenu
    :hide-when-active-nodes="['image', 'button']"
    :hide-when-active-marks="['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.

```vue theme={null}
<script setup lang="ts">
import { BubbleMenu, bubbleMenuTriggers } from '@vuemaildev/editor/ui';
import { PluginKey } from '@tiptap/pm/state';

const myPluginKey = new PluginKey('myCustomMenu');
const imageTrigger = bubbleMenuTriggers.node('image');
</script>

<template>
  <BubbleMenu :trigger="imageTrigger" :plugin-key="myPluginKey" placement="top">
    <!-- your custom menu content -->
  </BubbleMenu>
</template>
```

**trigger** `TriggerFn`

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

**plugin-key** `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`.

**hide-when-active-nodes** `string[]` (default: \[])

Node types that prevent the menu from showing.

**hide-when-active-marks** `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.

**@hide** `() =\> void`

Emitted 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:

```vue my-link-menu.vue theme={null}
<script setup lang="ts">
import { BubbleMenu, bubbleMenuTriggers } from '@vuemaildev/editor/ui';
import { PluginKey } from '@tiptap/pm/state';

const linkKey = new PluginKey('myLinkMenu');
const linkTrigger = bubbleMenuTriggers.nodeWithoutSelection('link');
</script>

<template>
  <BubbleMenu :trigger="linkTrigger" :plugin-key="linkKey" placement="top">
    <BubbleMenu.LinkToolbar>
      <BubbleMenu.LinkEditLink />
      <BubbleMenu.LinkOpenLink />
      <BubbleMenu.LinkUnlink />
    </BubbleMenu.LinkToolbar>
    <BubbleMenu.LinkForm />
  </BubbleMenu>
</template>
```

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:

```vue custom-toolbar-item.vue theme={null}
<script setup lang="ts">
import { useBubbleMenuContext } from '@vuemaildev/editor/ui';

const menu = useBubbleMenuContext();
</script>

<template>
  <button type="button" @click="menu.setIsEditing(true)">
    {{ menu.isEditing ? 'Editing' : 'Edit' }}
  </button>
</template>
```

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

> [!NOTE]
>
> The context's fields are reactive getters: read them from the context, as in `menu.isEditing`,
> instead of destructuring them, to keep them up to date.

***

## CSS import

```ts theme={null}
import '@vuemaildev/editor/styles/bubble-menu.css';
```

> [!TIP]
>
> `@vuemaildev/editor/themes/default.css` bundles all UI component styles.
> Unless you need to cherry-pick, use this single import:
>
> ```ts theme={null}
> import '@vuemaildev/editor/themes/default.css';
> ```


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