The EditorToolbar component is used to display a toolbar of formatting buttons that automatically sync their active state with the editor content. It must be used inside an Editor component's default slot to have access to the editor instance.
<script setup lang="ts">
import type { EditorToolbarItem } from '@nuxt/ui'
const value = ref(`# Toolbar
Select some text to see the formatting toolbar appear above your selection.`)
const items: EditorToolbarItem[][] = [
[
{
icon: 'i-lucide-heading',
content: {
align: 'start'
},
items: [
{
kind: 'heading',
level: 1,
icon: 'i-lucide-heading-1',
label: 'Heading 1'
},
{
kind: 'heading',
level: 2,
icon: 'i-lucide-heading-2',
label: 'Heading 2'
},
{
kind: 'heading',
level: 3,
icon: 'i-lucide-heading-3',
label: 'Heading 3'
},
{
kind: 'heading',
level: 4,
icon: 'i-lucide-heading-4',
label: 'Heading 4'
}
]
}
],
[
{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold'
},
{
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic'
},
{
kind: 'mark',
mark: 'underline',
icon: 'i-lucide-underline'
},
{
kind: 'mark',
mark: 'strike',
icon: 'i-lucide-strikethrough'
},
{
kind: 'mark',
mark: 'code',
icon: 'i-lucide-code'
}
]
]
</script>
<template>
<UEditor v-slot="{ editor }" v-model="value" content-type="markdown" class="w-full min-h-21">
<UEditorToolbar :editor="editor" :items="items" layout="bubble" />
</UEditor>
</template>
Use the items prop as an array of objects with the following properties:
label?: stringicon?: stringcolor?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"variant?: "solid" | "outline" | "soft" | "ghost" | "link" | "subtle"size?: "xs" | "sm" | "md" | "lg" | "xl"kind?: "mark" | "textAlign" | "heading" | "link" | "image" | "blockquote" | "bulletList" | "orderedList" | "codeBlock" | "horizontalRule" | "paragraph" | "undo" | "redo" | "clearFormatting" | "duplicate" | "delete" | "moveUp" | "moveDown" | "suggestion" | "mention" | "emoji"disabled?: booleanloading?: booleanactive?: booleanslot?: stringonClick?: (e: MouseEvent) => voiditems?: EditorToolbarItem[] | EditorToolbarItem[][]class?: anykind property references a handler defined in the Editor component. Handlers wrap TipTap commands and manage their state (active, disabled, etc.). The Editor provides default handlers for common actions (mark, heading, link, etc.), but you can add custom handlers using the handlers prop on the Editor component.kind property for editor-specific actions, additional properties may be required:kind: "mark": mark: "bold" | "italic" | "strike" | "code" | "underline"kind: "textAlign": align: "left" | "center" | "right" | "justify"kind: "heading": level: 1 | 2 | 3 | 4 | 5 | 6kind: "link": href?: stringkind: "image": src?: stringkind: "duplicate" | "delete" | "moveUp" | "moveDown": pos: numberkind: "clearFormatting" | "suggestion": pos?: numberYou can pass any property from the Button component such as color, variant, size, etc. but also active-color and active-variant as items with a kind property automatically sync their active state with the editor.
<script setup lang="ts">
import type { EditorToolbarItem } from '@nuxt/ui'
import TextAlign from '@tiptap/extension-text-align'
const value = ref(`# Nuxt UI Editor
This toolbar showcases **all available formatting options** using built-in handlers. Try the different controls to see them in action!
## Text Formatting
You can apply **bold**, *italic*, <u>underline</u>, ~~strikethrough~~, and \`inline code\` formatting to your text.
## Block Types
### Lists
Here's a bullet list:
- First item
- Second item
- Third item
And a numbered list:
1. Step one
2. Step two
3. Step three
### Blockquote
> This is a blockquote. Use it for highlighting important information or quotes from other sources.
### Code Block
\`\`\`
// Code blocks are perfect for technical content
function hello() {
console.log('Hello, world!')
}
\`\`\`
---
Use the horizontal rule above to create visual separations in your content. Try all the toolbar controls to explore the full range of formatting options!`)
const toolbarItems: EditorToolbarItem[][] = [
// History controls
[{
kind: 'undo',
icon: 'i-lucide-undo'
}, {
kind: 'redo',
icon: 'i-lucide-redo'
}],
// Block types
[{
icon: 'i-lucide-heading',
content: {
align: 'start'
},
items: [{
type: 'label',
label: 'Headings'
}, {
kind: 'heading',
level: 1,
icon: 'i-lucide-heading-1',
label: 'Heading 1'
}, {
kind: 'heading',
level: 2,
icon: 'i-lucide-heading-2',
label: 'Heading 2'
}, {
kind: 'heading',
level: 3,
icon: 'i-lucide-heading-3',
label: 'Heading 3'
}, {
kind: 'heading',
level: 4,
icon: 'i-lucide-heading-4',
label: 'Heading 4'
}]
}, {
icon: 'i-lucide-list',
content: {
align: 'start'
},
items: [{
kind: 'bulletList',
icon: 'i-lucide-list',
label: 'Bullet List'
}, {
kind: 'orderedList',
icon: 'i-lucide-list-ordered',
label: 'Ordered List'
}]
}, {
kind: 'blockquote',
icon: 'i-lucide-text-quote'
}, {
kind: 'codeBlock',
icon: 'i-lucide-square-code'
}, {
kind: 'horizontalRule',
icon: 'i-lucide-separator-horizontal'
}],
// Text formatting
[{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold'
}, {
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic'
}, {
kind: 'mark',
mark: 'underline',
icon: 'i-lucide-underline'
}, {
kind: 'mark',
mark: 'strike',
icon: 'i-lucide-strikethrough'
}, {
kind: 'mark',
mark: 'code',
icon: 'i-lucide-code'
}],
// Link
[{
kind: 'link',
icon: 'i-lucide-link'
}],
// Text alignment
[{
icon: 'i-lucide-align-justify',
content: {
align: 'end'
},
items: [{
kind: 'textAlign',
align: 'left',
icon: 'i-lucide-align-left',
label: 'Align Left'
}, {
kind: 'textAlign',
align: 'center',
icon: 'i-lucide-align-center',
label: 'Align Center'
}, {
kind: 'textAlign',
align: 'right',
icon: 'i-lucide-align-right',
label: 'Align Right'
}, {
kind: 'textAlign',
align: 'justify',
icon: 'i-lucide-align-justify',
label: 'Align Justify'
}]
}]
]
</script>
<template>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
:extensions="[TextAlign.configure({ types: ['heading', 'paragraph'] })]"
class="w-full min-h-21"
>
<UEditorToolbar :editor="editor" :items="toolbarItems" class="px-8" />
</UEditor>
</template>
items prop to create separated groups of items.items array of objects with the same properties as the items prop to create a DropdownMenu.Use the layout prop to change how the toolbar is displayed. Defaults to fixed.
| Layout | Description |
|---|---|
fixed | Always visible toolbar, typically placed above the editor |
bubble | Contextual menu that appears when text is selected |
floating | Menu that appears on empty lines or blocks |
<script setup lang="ts">
import type { EditorToolbarItem } from '@nuxt/ui'
defineProps<{
layout: 'fixed' | 'bubble' | 'floating'
}>()
const value = ref(`Select this text to see the bubble menu appear.
Click on an empty line to see the floating menu.
`)
const items: EditorToolbarItem[][] = [[{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold'
}, {
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic'
}, {
kind: 'mark',
mark: 'code',
icon: 'i-lucide-code'
}]]
</script>
<template>
<UEditor v-slot="{ editor }" v-model="value" class="w-full min-h-21">
<UEditorToolbar
:editor="editor"
:items="items"
:layout="layout"
/>
</UEditor>
</template>
When using bubble or floating layouts, use the should-show prop to control when the toolbar appears. This function receives context about the editor state and returns a boolean.
<script setup lang="ts">
import type { EditorToolbarItem } from '@nuxt/ui'
const value = ref(`Select text that is longer than 10 characters to see the bubble menu.
Short text won't trigger the menu.`)
const toolbarItems: EditorToolbarItem[][] = [[{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold'
}, {
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic'
}]]
</script>
<template>
<UEditor v-slot="{ editor }" v-model="value" class="w-full min-h-21">
<UEditorToolbar
:editor="editor"
:items="toolbarItems"
layout="bubble"
:should-show="({ view, state }) => {
const { selection } = state
const { from, to } = selection
const text = state.doc.textBetween(from, to)
return view.hasFocus() && !selection.empty && text.length > 10
}"
/>
</UEditor>
</template>
When using bubble or floating layouts, use the options prop to customize the positioning behavior using Floating UI options.
<template>
<UEditorToolbar
:editor="editor"
:items="items"
layout="bubble"
:options="{
placement: 'top',
offset: 8,
flip: { padding: 8 },
shift: { padding: 8 }
}"
/>
</template>
Use the color and variant props to customize the toolbar button styles.
<script setup lang="ts">
const editor = ref({})
const items = ref([
[
{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold'
}
]
])
</script>
<template>
<UEditorToolbar color="primary" variant="soft" :editor="editor" :items="items" />
</template>
Use the active-color and active-variant props to customize the active state styling.
<script setup lang="ts">
const editor = ref({})
const items = ref([
[
{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold'
}
]
])
</script>
<template>
<UEditorToolbar active-color="success" active-variant="solid" :editor="editor" :items="items" />
</template>
Use the size prop to change the size of toolbar buttons. Defaults to sm.
<script setup lang="ts">
const editor = ref({})
const items = ref([
[
{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold'
}
]
])
</script>
<template>
<UEditorToolbar size="md" :editor="editor" :items="items" />
</template>
You can use slots to customize specific toolbar items.
<script setup lang="ts">
import type { EditorToolbarItem } from '@nuxt/ui'
import EditorLinkPopover from '~/components/editor/EditorLinkPopover.vue'
const value = ref(`Select text and click the link button to add a link with the custom popover.
You can also edit existing links like [this one](https://ui.nuxt.com).`)
const toolbarItems = [[{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold'
}, {
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic'
}, {
slot: 'link' as const
}]] satisfies EditorToolbarItem[][]
</script>
<template>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
class="w-full min-h-21"
>
<UEditorToolbar :editor="editor" :items="toolbarItems">
<template #link>
<EditorLinkPopover :editor="editor" auto-open />
</template>
</UEditorToolbar>
</UEditor>
</template>
Use the slot property on an item to specify a named slot, then provide a matching template:
<UEditorToolbar :editor="editor" :items="[[{ slot: 'link' }]]">
<template #link>
<MyCustomLinkButton :editor="editor" />
</template>
</UEditorToolbar>
Create context-specific toolbars that appear only for certain node types.
<script setup lang="ts">
import type { EditorToolbarItem } from '@nuxt/ui'
const value = ref(`Click on the image below to see the image-specific toolbar:

The toolbar only appears when an image is selected.`)
const imageToolbarItems: EditorToolbarItem[][] = [[{
icon: 'i-lucide-download',
label: 'Download'
}, {
icon: 'i-lucide-trash',
label: 'Delete'
}]]
</script>
<template>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
class="w-full min-h-21"
>
<UEditorToolbar
:editor="editor"
:items="imageToolbarItems"
layout="bubble"
:should-show="({ editor, view }) => {
return editor.isActive('image') && view.hasFocus()
}"
/>
</UEditor>
</template>
| Prop | Default | Type |
|---|---|---|
as | 'div' | anyThe element or component this component should render as. |
editor | Editor | |
color | 'neutral' | "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral"The color of the toolbar controls. |
variant | 'ghost' | "solid" | "outline" | "soft" | "subtle" | "ghost" | "link"The variant of the toolbar controls. |
activeColor | 'primary' | "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral"The color of the active toolbar control. |
activeVariant | 'soft' | "solid" | "outline" | "soft" | "subtle" | "ghost" | "link"The variant of the active toolbar control. |
size | 'sm' | "xs" | "sm" | "md" | "lg" | "xl"The size of the toolbar controls. |
items | EditorToolbarItem<EditorCustomHandlers>[] | EditorToolbarItem<EditorCustomHandlers>[][]
| |
layout | 'fixed' | "fixed" | "floating" | "bubble" |
ui | { root?: ClassNameValue; base?: ClassNameValue; group?: ClassNameValue; separator?: ClassNameValue; } |
| Slot | Type |
|---|---|
default | {} |
item | { item: EditorToolbarItem<EditorCustomHandlers>; } & SlotPropsProps |
| Event | Type |
|---|
export default defineAppConfig({
ui: {
editorToolbar: {
slots: {
root: 'focus:outline-none',
base: 'flex items-stretch gap-1.5',
group: 'flex items-center gap-0.5',
separator: 'w-px self-stretch bg-border'
},
variants: {
layout: {
bubble: {
base: 'bg-default border border-default rounded-lg p-1'
},
floating: {
base: 'bg-default border border-default rounded-lg p-1'
},
fixed: {
base: ''
}
}
}
}
}
})
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
editorToolbar: {
slots: {
root: 'focus:outline-none',
base: 'flex items-stretch gap-1.5',
group: 'flex items-center gap-0.5',
separator: 'w-px self-stretch bg-border'
},
variants: {
layout: {
bubble: {
base: 'bg-default border border-default rounded-lg p-1'
},
floating: {
base: 'bg-default border border-default rounded-lg p-1'
},
fixed: {
base: ''
}
}
}
}
}
})
]
})