---
title: Toolbar
description: The toolbar plugin shows Notion's floating toolbar over text selections, with Turn into, a link panel, a button for every mark that declares one, and text and background colors.
icon: bold
---

`toolbarPlugin` shows Notion's formatting toolbar above a text selection: `[Kind ⌄] | Link | B I U S </> | A ⌄`. The mark buttons come from the mark definitions: every mark with a `toolbar` entry gets one.

```svelte
<script lang="ts">
	import { Edytor, toolbarPlugin } from 'edytor';
</script>

<Edytor plugins={[toolbarPlugin]} />
```

`createToolbarPlugin({ toolbar })` builds one that renders your markup (see [Custom markup](#custom-markup)). It formats through [`richTextOperations`](/docs/plugins/rich-text#formatting-from-code), so it relies on the rich text plugin (on by default), or on your own plugin that defines the `link`, `color` and `highlight` marks if you want the link and color panels to work.

## What it shows

From left to right:

1. **The kind button**, labeled with the block's kind ("Heading 2", "Text"…): the preset sharing the most data with the block, or drawn with the same element (a stored `h5` heading, drawn as an `h3`, is "Heading 3"). It opens a "Turn into" panel listing the kinds that keep content, with ✓ on the current one. Picking one converts every block the selection touches, nested ones included, as one undo step, and keeps the selection.
2. **Link** opens a link panel: a URL field (`data-testid="toolbar-link-input"`) showing the selection's current link, with Apply and Remove. <kbd>Enter</kbd> in the field applies and closes the panel. Applying an empty field removes the link.
3. **One button per mark** whose definition has `toolbar: { label, icon }`, in registration order. With the rich text plugin: bold, italic, underline, strike and code. A button toggles its mark over the selection, and shows pressed (`aria-pressed="true"`, in blue) when the mark covers the whole selection.
4. **A ⌄** (Color) opens Notion's colors: ten text colors and ten background colors. A text color sets the `color` mark, a background the `highlight` mark, and Default removes that mark. The palette is exported as `TOOLBAR_COLORS` (`{ name, text, background }`).

One panel is open at a time; clicking its button again closes it. Panels drop below the bar, over the text.

| Color   | Text      | Background |
| ------- | --------- | ---------- |
| Default | none      | none       |
| Gray    | `#7d7a75` | `#f0efed`  |
| Brown   | `#9f765a` | `#f5ede9`  |
| Orange  | `#d27b2d` | `#fbebde`  |
| Yellow  | `#cb9434` | `#f9f3dc`  |
| Green   | `#50946e` | `#e8f1ec`  |
| Blue    | `#387dc9` | `#e5f2fc`  |
| Purple  | `#9a6bb4` | `#f3ebf9`  |
| Pink    | `#c14c8a` | `#fae9f1`  |
| Red     | `#cf5148` | `#fce9e7`  |

The values are stored in the marks as written, so documents keep them if you restyle the editor.

Add a button for your own mark by declaring `toolbar` on it:

```ts
marks: {
	keyboard: { tag: 'kbd', toolbar: { label: 'Keyboard', icon: '⌨' } }
}
```

The `icon` is text: a letter, a symbol or an emoji.

## When it shows

The toolbar shows when all of these hold:

- the editor is not readonly (turning it readonly hides the toolbar; it shows again over the same selection when the editor is editable);
- the selection is a non-collapsed text range;
- no whole block and no inline atom is selected;
- the selection is not inside a void or an island block (such as a code block).

It follows the selection as it changes, and repositions on scroll, resize and edits. Buttons keep the selection: clicking one does not move the caret, and the selection is restored after the format is applied.

A URL typed in the link panel goes through the same sanitization as every link: a `javascript:` or other unsafe scheme is ignored. Colors go through `setMarkValueAtRange` and `removeMarkAtRange` (see [Formatting from code](/docs/plugins/rich-text#formatting-from-code)).

## Placement and styling

The toolbar mounts in the editor's overlay, in a `position: fixed` host with the attribute `data-edytor-toolbar-host` and `z-index: 60`. It is centered above the selection, or placed below it when there is no room above, and kept inside the viewport.

Its markup and styles are built in, after Notion's. The bar has `data-edytor-toolbar-bar` and `data-testid="selection-toolbar"`, the Link button `toolbar-link`, the link panel's buttons `toolbar-link-apply` and `toolbar-link-remove`, and each mark button the class `mark-<name>` and a `data-testid` of `toolbar-<name>`.

## Custom markup

`createToolbarPlugin` takes a `toolbar` snippet that replaces the whole bar. The plugin keeps deciding when it shows (the rules above), follows the selection and places it.

| Prop | Type | Default | Description |
| - | - | - | - |
| `toolbar?` | `Snippet<[ToolbarController]>` | - | Replaces the toolbar. Rendered while controller.isVisible, centered above the selection. |

The snippet receives the `ToolbarController`. Every action keeps and restores the selection:

| Prop | Type | Default | Description |
| - | - | - | - |
| `marks?` | `{ mark, label, icon }[]` | - | The marks that declare a toolbar button, in registration order. |
| `toggleMark(mark)?` | `(mark: string) => void` | - | Toggle a mark over the selection. |
| `isActive(mark)?` | `(mark: string) => boolean` | - | Whether the mark covers every character of the selection, for a pressed state. |
| `kinds?` | `KindRow[]` | - | The kinds the block may turn into while keeping its content. |
| `currentKind?` | `KindRow \| undefined` | - | The row naming the selection's block: of its kind's rows, the one whose preset data shares the most values with the block's (the first on a tie). |
| `turnInto(kind)?` | `(kind: KindRow) => void` | - | Convert every block the selection touches, as one undo step. |
| `setColor(mark, value)?` | `('color' \| 'highlight', value: string \| null) => void` | - | Set the text or background color; null removes it. |
| `linkUrl?` | `string` | - | The selection's current link, or the URL being typed. |
| `setLinkUrl(value)?` | `(value: string) => void` | - | Update linkUrl, for a link field. |
| `applyLink()?` | `() => void` | - | Link the selection to linkUrl; an empty URL removes the link. |
| `removeLink()?` | `() => void` | - | Remove the link from the selection. |
| `panel?` | `'turn' \| 'link' \| 'color' \| null` | - | The open panel, for your own panels. |
| `togglePanel(panel)?` | `(panel: 'turn' \| 'link' \| 'color') => void` | - | Open a panel, or close it when it is open. |

```svelte title="Editor.svelte"
<script lang="ts">
	import { Edytor, createToolbarPlugin, type ToolbarController } from 'edytor';

	const plugins = [createToolbarPlugin({ toolbar })];
	const keep = (event: MouseEvent) => event.preventDefault();
</script>

{#snippet toolbar(controller: ToolbarController)}
	<div class="bar" data-edytor-toolbar-bar>
		{#each controller.marks as { mark, label, icon } (mark)}
			<button title={label} onmousedown={keep} onclick={() => controller.toggleMark(mark)}>{icon}</button>
		{/each}
		<button onmousedown={keep} onclick={() => controller.setColor('highlight', '#fbf3db')}>Highlight</button>
	</div>
{/snippet}

<Edytor {plugins} />
```

Call `preventDefault` on `mousedown` in every button, or the click moves the selection before the action runs. The plugin places the host by the element marked `data-edytor-toolbar-bar` (else your snippet's first element), so a panel that opens below the bar does not push the bar up. `TOOLBAR_COLORS` holds Notion's palette for your own color panel. See [Menus and handles](/docs/customization/menus) for the other menus.
