EditorMentionMenu

GitHub
A mention menu that displays user suggestions when typing the @ character in the editor.

Usage

The EditorMentionMenu component is used to display a menu of user suggestions when typing the @ character in the editor. Mentions are inserted as inline elements that can be styled and linked. It must be used inside an Editor component's default slot to have access to the editor instance.

Type @ followed by a name to search and insert mentions.

<script setup lang="ts">
import type { EditorMentionMenuItem } from '@nuxt/ui'

const value = ref(`# Mention Menu

Type @ to mention someone and select from the list of available users.`)

const items: EditorMentionMenuItem[] = [
  {
    label: 'benjamincanac',
    avatar: {
      src: 'https://avatars.githubusercontent.com/u/739984?v=4'
    }
  },
  {
    label: 'atinux',
    avatar: {
      src: 'https://avatars.githubusercontent.com/u/904724?v=4'
    }
  },
  {
    label: 'danielroe',
    avatar: {
      src: 'https://avatars.githubusercontent.com/u/28706372?v=4'
    }
  },
  {
    label: 'pi0',
    avatar: {
      src: 'https://avatars.githubusercontent.com/u/5158436?v=4'
    }
  }
]

// SSR-safe function to append menus to body (avoids z-index issues in docs)
const appendToBody = false ? () => document.body : undefined
</script>

<template>
  <UEditor
    v-slot="{ editor }"
    v-model="value"
    content-type="markdown"
    placeholder="Type @ to mention someone..."
    class="w-full min-h-21"
  >
    <UEditorMentionMenu :editor="editor" :items="items" :append-to="appendToBody" />
  </UEditor>
</template>
The menu filters items as you type and supports keyboard navigation (arrow keys, enter to select, escape to close).
Learn more about the Mention extension in the TipTap documentation.

Items

Use the items prop as an array of objects with the following properties:

  • label: string
  • avatar?: AvatarProps
  • icon?: string
  • description?: string
  • disabled?: boolean
<script setup lang="ts">
import type { EditorMentionMenuItem } from '@nuxt/ui'

const value = ref(`Mention a team member or channel: `)

const mentionItems: EditorMentionMenuItem[] = [{
  label: 'benjamincanac',
  description: 'Benjamin Canac',
  avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' }
}, {
  label: 'atinux',
  description: 'Sébastien Chopin',
  avatar: { src: 'https://avatars.githubusercontent.com/u/904724?v=4' }
}, {
  label: 'general',
  description: 'General channel',
  icon: 'i-lucide-hash'
}, {
  label: 'engineering',
  description: 'Engineering team',
  icon: 'i-lucide-users'
}]
</script>

<template>
  <UEditor v-slot="{ editor }" v-model="value" placeholder="Type @ to mention..." class="w-full min-h-21">
    <UEditorMentionMenu :editor="editor" :items="mentionItems" />
  </UEditor>
</template>
Use avatars for user mentions and icons for entities like teams, channels, or tags.

Char

Use the char prop to change the trigger character. Defaults to @.

<template>
  <UEditorMentionMenu :editor="editor" :items="channels" char="#" />
</template>
Use # for channels or tags, + for adding team members, etc.

Options

Use the options prop to customize the positioning behavior using Floating UI options.

<template>
  <UEditorMentionMenu
    :editor="editor"
    :items="items"
    :options="{
      placement: 'bottom-start',
      offset: 4
    }"
  />
</template>

API

Props

Prop Default Type
editorEditor
char'@' string

The trigger character (e.g., '/', '@', ':')

pluginKey'mentionMenu' string

Plugin key to identify this menu

items EditorMentionMenuItem[] | EditorMentionMenuItem[][]

The items to display (can be a flat array or grouped)

limit42 number

Maximum number of items to display

options{ strategy: 'absolute', placement: 'bottom-start', offset: 8, shift: { padding: 8 } } FloatingUIOptions

The options for positioning the menu. Those are passed to Floating UI and include options for the placement, offset, flip, shift, size, autoPlacement, hide, and inline middleware.

appendTo HTMLElement | (): HTMLElement

The DOM element to append the menu to. Default is the editor's parent element.

Sometimes the menu needs to be appended to a different DOM context due to accessibility, clipping, or z-index issues.

ui { content?: ClassNameValue; viewport?: ClassNameValue; group?: ClassNameValue; label?: ClassNameValue; separator?: ClassNameValue; item?: ClassNameValue; itemLeadingIcon?: ClassNameValue; itemLeadingAvatar?: ClassNameValue; itemLeadingAvatarSize?: ClassNameValue; itemWrapper?: ClassNameValue; itemLabel?: ClassNameValue; itemDescription?: ClassNameValue; itemLabelExternalIcon?: ClassNameValue; }

Theme

app.config.ts
export default defineAppConfig({
  ui: {
    editorMentionMenu: {
      slots: {
        content: 'min-w-48 max-w-60 max-h-96 bg-default shadow-lg rounded-md ring ring-default overflow-hidden data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-dropdown-menu-content-transform-origin) flex flex-col',
        viewport: 'relative divide-y divide-default scroll-py-1 overflow-y-auto flex-1',
        group: 'p-1 isolate',
        label: 'w-full flex items-center font-semibold text-highlighted p-1.5 text-xs gap-1.5',
        separator: '-mx-1 my-1 h-px bg-border',
        item: 'group relative w-full flex items-start select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75 p-1.5 text-sm gap-1.5',
        itemLeadingIcon: 'shrink-0 size-5 flex items-center justify-center text-base',
        itemLeadingAvatar: 'shrink-0',
        itemLeadingAvatarSize: '2xs',
        itemWrapper: 'flex-1 flex flex-col text-start min-w-0',
        itemLabel: 'truncate',
        itemDescription: 'truncate text-muted',
        itemLabelExternalIcon: 'inline-block size-3 align-top text-dimmed'
      },
      variants: {
        active: {
          true: {
            item: 'text-highlighted before:bg-elevated/75',
            itemLeadingIcon: 'text-default'
          },
          false: {
            item: [
              'text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50',
              'transition-colors before:transition-colors'
            ],
            itemLeadingIcon: [
              'text-dimmed group-data-highlighted:not-group-data-disabled:text-default',
              'transition-colors'
            ]
          }
        }
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        editorMentionMenu: {
          slots: {
            content: 'min-w-48 max-w-60 max-h-96 bg-default shadow-lg rounded-md ring ring-default overflow-hidden data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-dropdown-menu-content-transform-origin) flex flex-col',
            viewport: 'relative divide-y divide-default scroll-py-1 overflow-y-auto flex-1',
            group: 'p-1 isolate',
            label: 'w-full flex items-center font-semibold text-highlighted p-1.5 text-xs gap-1.5',
            separator: '-mx-1 my-1 h-px bg-border',
            item: 'group relative w-full flex items-start select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75 p-1.5 text-sm gap-1.5',
            itemLeadingIcon: 'shrink-0 size-5 flex items-center justify-center text-base',
            itemLeadingAvatar: 'shrink-0',
            itemLeadingAvatarSize: '2xs',
            itemWrapper: 'flex-1 flex flex-col text-start min-w-0',
            itemLabel: 'truncate',
            itemDescription: 'truncate text-muted',
            itemLabelExternalIcon: 'inline-block size-3 align-top text-dimmed'
          },
          variants: {
            active: {
              true: {
                item: 'text-highlighted before:bg-elevated/75',
                itemLeadingIcon: 'text-default'
              },
              false: {
                item: [
                  'text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50',
                  'transition-colors before:transition-colors'
                ],
                itemLeadingIcon: [
                  'text-dimmed group-data-highlighted:not-group-data-disabled:text-default',
                  'transition-colors'
                ]
              }
            }
          }
        }
      }
    })
  ]
})

Changelog

No recent changes