Files
2026-04-17 23:26:01 +00:00

4.1 KiB

Editor Layout

Build a rich text editor with toolbars, slash commands, mentions, and drag-and-drop.

Component tree

UApp
├── UHeader
├── UMain
│   └── NuxtPage
│       └── UContainer
│           └── UEditor
│               ├── UEditorToolbar (fixed / bubble / floating)
│               ├── UEditorDragHandle
│               ├── UEditorSuggestionMenu
│               ├── UEditorMentionMenu
│               └── UEditorEmojiMenu
└── UFooter

Page

<script setup lang="ts">
const content = ref({
  type: 'doc',
  content: [
    {
      type: 'heading',
      attrs: { level: 1 },
      content: [{ type: 'text', text: 'Hello World' }]
    },
    {
      type: 'paragraph',
      content: [{ type: 'text', text: 'Start writing...' }]
    }
  ]
})
</script>

<template>
  <UPage>
    <UPageHeader title="Editor">
      <template #actions>
        <UButton label="Save" icon="i-lucide-save" />
      </template>
    </UPageHeader>

    <UPageBody>
      <UEditor v-slot="{ editor }" v-model="content">
        <UEditorToolbar :editor="editor" />
        <UEditorSuggestionMenu :editor="editor" />
        <UEditorMentionMenu
          :editor="editor"
          :items="[
            { label: 'Benjamin', avatar: { src: 'https://github.com/benjamincanac.png' } },
            { label: 'Sébastien', avatar: { src: 'https://github.com/atinux.png' } }
          ]"
        />
        <UEditorEmojiMenu :editor="editor" />
        <UEditorDragHandle :editor="editor" />
      </UEditor>
    </UPageBody>
  </UPage>
</template>

If you encounter prosemirror-related errors, add prosemirror packages to vite.optimizeDeps.include in nuxt.config.ts.

Key components

  • UEditor — Rich text editor (v-model accepts JSON, HTML, or markdown via content-type prop)
  • UEditorToolbar — Toolbar with layout: 'fixed' (default), 'bubble' (on selection), 'floating' (on empty lines)
  • UEditorDragHandle — Block drag-and-drop handle
  • UEditorSuggestionMenu — Slash command menu
  • UEditorMentionMenu — @ mention menu
  • UEditorEmojiMenu — Emoji picker

Toolbar modes

<!-- Fixed (default) -->
<UEditor v-model="content">
  <UEditorToolbar />
</UEditor>

<!-- Bubble (appears on text selection) -->
<UEditor v-model="content">
  <UEditorToolbar layout="bubble" />
</UEditor>

<!-- Floating (appears on empty lines) -->
<UEditor v-model="content">
  <UEditorToolbar layout="floating" />
</UEditor>

Content types

<!-- JSON (default) -->
<UEditor v-model="jsonContent" />

<!-- HTML -->
<UEditor v-model="htmlContent" content-type="html" />

<!-- Markdown -->
<UEditor v-model="markdownContent" content-type="markdown" />

With document sidebar

Combine with Dashboard components for a multi-document editor with a sidebar.

<template>
  <UDashboardGroup>
    <UDashboardSidebar collapsible resizable>
      <template #header>
        <UButton icon="i-lucide-plus" label="New document" block />
      </template>

      <template #default>
        <UNavigationMenu
          :items="documents.map(doc => ({
            label: doc.title,
            to: `/editor/${doc.id}`,
            icon: 'i-lucide-file-text'
          }))"
          orientation="vertical"
        />
      </template>
    </UDashboardSidebar>

    <slot />
  </UDashboardGroup>
</template>
<script setup lang="ts">
definePageMeta({ layout: 'editor' })

const content = ref({ type: 'doc', content: [] })
</script>

<template>
  <UDashboardPanel>
    <template #header>
      <UDashboardNavbar title="Editor">
        <template #right>
          <UButton label="Save" icon="i-lucide-save" />
        </template>
      </UDashboardNavbar>
    </template>

    <UContainer class="py-8">
      <UEditor v-slot="{ editor }" v-model="content">
        <UEditorToolbar :editor="editor" />
        <UEditorSuggestionMenu :editor="editor" />
        <UEditorEmojiMenu :editor="editor" />
        <UEditorDragHandle :editor="editor" />
      </UEditor>
    </UContainer>
  </UDashboardPanel>
</template>