The Editor component is now implemented! Check it out.
v2.1.16
/
  • Get Started
  • Components
  • Composables
  • Typography
  • GitHub
  • Layout
  • App
  • Container
  • Error
  • SidebarLayout
  • Element
  • Advice
  • Alert
  • Avatar
  • AvatarGroup
  • Badge
  • Banner
  • Button
  • Calendar
  • Card
  • Chip
  • Collapsible
  • Countdown
  • FieldGroup
  • Kbd
  • Progress
  • Separator
  • Skeleton
  • Form
  • Checkbox
  • CheckboxGroup
  • ColorPicker
  • FileUpload
  • Form
  • FormField
  • Input
  • InputDate
  • InputMenu
  • InputNumber
  • InputTags
  • InputTime
  • PinInput
  • RadioGroup
  • Range
  • Select
  • SelectMenu
  • Switch
  • Textarea
  • Data
  • Accordion
  • DescriptionList
  • Empty
  • Table
  • TableWrapper
  • Timeline
  • User
  • Navigation
  • Breadcrumb
  • CommandPalette
  • Link
  • NavigationMenu
  • Pagination
  • Stepper
  • Tabs
  • Overlay
  • ContextMenu
  • DropdownMenu
  • Modal
  • Popover
  • Slideover
  • Toast
  • Tooltip
  • Page
  • PageCard
  • PageColumns
  • PageGrid
  • PageLinks
  • PageList
  • Dashboard
  • DashboardGroup
  • DashboardSearch
  • DashboardSearchButton
  • AI Chat
  • soonChatMessage
  • soonChatMessages
  • soonChatPalette
  • soonChatPrompt
  • soonChatPromptSubmit
  • Editor
  • NewEditor
  • NewEditorDragHandle
  • NewEditorEmojiMenu
  • NewEditorMentionMenu
  • NewEditorSuggestionMenu
  • NewEditorToolbar
  • Content
  • ContentSearch
  • ContentSearchButton
  • ContentSurround
  • ContentToc
  • Color Mode
  • ColorModeAvatar
  • ColorModeButton
  • ColorModeImage
  • ColorModeSelect
  • ColorModeSwitch
  • i18n
  • LocaleSelect
  • b24icons
  • b24jssdk
Use our Nuxt starter
v2.1.16
  • Docs
  • Components
  • Composables
  • Typography

EditorSuggestionMenu

A slash command menu that displays formatting and action suggestions when typing /.
GitHub
Demo
Nuxt UI

Usage

The EditorSuggestionMenu component displays a menu of formatting and action suggestions when typing a trigger character in the editor and executes the corresponding handler when an item is selected.

It uses the useEditorMenu composable built on top of TipTap's Suggestion utility to filter items as you type and support keyboard navigation (arrow keys, enter to select, escape to close).
It must be used inside an Editor component's default slot to have access to the editor instance.
<script setup lang="ts">
import type { EditorSuggestionMenuItem } from '@bitrix24/b24ui-nuxt'
import TextIcon from '@bitrix24/b24icons-vue/outline/TextIcon'
import BulletedListIcon from '@bitrix24/b24icons-vue/outline/BulletedListIcon'
import NumberedListIcon from '@bitrix24/b24icons-vue/outline/NumberedListIcon'
import QuoteIcon from '@bitrix24/b24icons-vue/outline/QuoteIcon'
import CodeIcon from '@bitrix24/b24icons-vue/common-service/CodeIcon'
import HrIcon from '@bitrix24/b24icons-vue/editor/HrIcon'

const value = ref(`# Suggestion Menu

Type / to open the suggestion menu and browse available formatting commands.`)

const items: EditorSuggestionMenuItem[][] = [
  [
    {
      type: 'label',
      label: 'Text'
    },
    {
      kind: 'paragraph',
      label: 'Paragraph',
      icon: TextIcon
    },
    {
      kind: 'heading',
      level: 1,
      label: 'Heading 1'
    },
    {
      kind: 'heading',
      level: 2,
      label: 'Heading 2'
    },
    {
      kind: 'heading',
      level: 3,
      label: 'Heading 3'
    }
  ],
  [
    {
      type: 'label',
      label: 'Lists'
    },
    {
      kind: 'bulletList',
      label: 'Bullet List',
      icon: BulletedListIcon
    },
    {
      kind: 'orderedList',
      label: 'Numbered List',
      icon: NumberedListIcon
    }
  ],
  [
    {
      type: 'label',
      label: 'Insert'
    },
    {
      kind: 'blockquote',
      label: 'Blockquote',
      icon: QuoteIcon
    },
    {
      kind: 'codeBlock',
      label: 'Code Block',
      icon: CodeIcon
    },
    {
      kind: 'horizontalRule',
      label: 'Divider',
      icon: HrIcon
    }
  ]
]

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

<template>
  <B24Editor
    v-slot="{ editor }"
    v-model="value"
    content-type="markdown"
    placeholder="Type / for commands..."
    class="w-full min-h-21"
  >
    <B24EditorSuggestionMenu :editor="editor" :items="items" :append-to="appendToBody" />
  </B24Editor>
</template>

Items

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

  • kind?: "textAlign" | "heading" | "link" | "image" | "blockquote" | "bulletList" | "orderedList" | "codeBlock" | "horizontalRule" | "paragraph" | "clearFormatting" | "duplicate" | "delete" | "moveUp" | "moveDown" | "suggestion" | "mention" | "emoji"
  • label?: string
  • description?: string
  • icon?: IconComponent
  • type?: "label" | "separator"
  • disabled?: boolean
<script setup lang="ts">
import type { EditorSuggestionMenuItem } from '@bitrix24/b24ui-nuxt'
import TextIcon from '@bitrix24/b24icons-vue/outline/TextIcon'
import BulletedListIcon from '@bitrix24/b24icons-vue/outline/BulletedListIcon'
import NumberedListIcon from '@bitrix24/b24icons-vue/outline/NumberedListIcon'
import QuoteIcon from '@bitrix24/b24icons-vue/outline/QuoteIcon'
import CodeIcon from '@bitrix24/b24icons-vue/common-service/CodeIcon'
import HrIcon from '@bitrix24/b24icons-vue/editor/HrIcon'

const value = ref(`Type / to see a list of commands.

You can customize the items with icons, labels, and descriptions.`)

const items: EditorSuggestionMenuItem[][] = [
  [
    {
      type: 'label',
      label: 'Text Styles'
    },
    {
      kind: 'paragraph',
      label: 'Paragraph',
      icon: TextIcon
    },
    {
      kind: 'heading',
      level: 1,
      label: 'Heading 1'
    },
    {
      kind: 'heading',
      level: 2,
      label: 'Heading 2'
    },
    {
      kind: 'heading',
      level: 3,
      label: 'Heading 3'
    }
  ],
  [
    {
      type: 'label',
      label: 'Lists'
    },
    {
      kind: 'bulletList',
      label: 'Bullet List',
      icon: BulletedListIcon
    },
    {
      kind: 'orderedList',
      label: 'Numbered List',
      icon: NumberedListIcon
    }
  ],
  [
    {
      type: 'label',
      label: 'Blocks'
    },
    {
      kind: 'blockquote',
      label: 'Blockquote',
      icon: QuoteIcon
    },
    {
      kind: 'codeBlock',
      label: 'Code Block',
      icon: CodeIcon
    },
    {
      kind: 'horizontalRule',
      label: 'Divider',
      icon: HrIcon
    }
  ]
]
</script>

<template>
  <B24Editor
    v-slot="{ editor }"
    v-model="value"
    content-type="markdown"
    placeholder="Type / for commands..."
    class="w-full min-h-19"
  >
    <B24EditorSuggestionMenu :editor="editor" :items="items" />
  </B24Editor>
</template>
You can also pass an array of arrays to the items prop to create separated groups of items.
Use type: 'label' for section headers and type: 'separator' for visual dividers to organize commands into logical groups for better discoverability.

Char

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

<template>
  <B24Editor v-slot="{ editor }">
    <B24EditorSuggestionMenu :editor="editor" :items="items" char=">" />
  </B24Editor>
</template>

Options

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

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

API

Props

Prop Default Type
items EditorSuggestionMenuItem<EditorCustomHandlers>[] | EditorSuggestionMenuItem<EditorCustomHandlers>[][]
  • type: "label"
  • label: string
  • class?: any
  • description?: string
  • icon?: IconComponent
  • disabled?: boolean
  • kind: "mark"
  • mark: "bold" | "italic" | "strike" | "code" | "underline"
  • align: "left" | "center" | "right" | "justify"
  • level: 1 | 2 | 3 | 4 | 5 | 6
  • src?: string
  • pos: number
editorEditor
char'/' string

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

pluginKey'suggestionMenu' string

Plugin key to identify this menu

filterFields['label'] string[]

Fields to filter items by.

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.

  • strategy?: Strategy
  • placement?: Placement
  • offset?: boolean | OffsetOptions
  • flip?: boolean | { mainAxis?: boolean ; crossAxis?: boolean | "alignment" | undefined; fallbackPlacements?: Placement[] | undefined; fallbackStrategy?: "bestFit" | "initialPlacement" | undefined; fallbackAxisSideDirection?: "end" | "start" | "none" | undefined; flipAlignment?: boolean | undefined; rootBoundary?: RootBoundary | undefined; elementContext?: ElementContext | undefined; altBoundary?: boolean | undefined; padding?: Padding | undefined; boundary?: Boundary | undefined; } | undefined
  • shift?: boolean | { mainAxis?: boolean ; crossAxis?: boolean | undefined; rootBoundary?: RootBoundary | undefined; elementContext?: ElementContext | undefined; altBoundary?: boolean | undefined; padding?: Padding | undefined; limiter?: { fn: (state: MiddlewareState) => Coords; options?: any; } | undefined; boundary?: Boundary | undefined; } | undefined
  • size?: boolean | { rootBoundary?: RootBoundary ; elementContext?: ElementContext | undefined; altBoundary?: boolean | undefined; padding?: Padding | undefined; boundary?: Boundary | undefined; apply?: ((args: { x: number; y: number; initialPlacement: Placement; placement: Placement; strategy: Strategy; middlewareData: MiddlewareData; rects: ElementRects; platform: Platform; elements: Elements; } & { availableWidth: number; availableHeight: number; }) => Promisable<void>) | undefined; } | undefined
  • autoPlacement?: boolean | { crossAxis?: boolean ; rootBoundary?: RootBoundary | undefined; elementContext?: ElementContext | undefined; altBoundary?: boolean | undefined; padding?: Padding | undefined; alignment?: Alignment | null | undefined; autoAlignment?: boolean | undefined; allowedPlacements?: Placement[] | undefined; boundary?: Boundary | undefined; } | undefined
  • hide?: boolean | { rootBoundary?: RootBoundary ; elementContext?: ElementContext | undefined; altBoundary?: boolean | undefined; padding?: Padding | undefined; strategy?: "referenceHidden" | "escaped" | undefined; boundary?: Boundary | undefined; } | undefined
  • inline?: boolean | InlineOptions
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.

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

Theme

app.config.ts
export default defineAppConfig({
  b24ui: {
    editorSuggestionMenu: {
      slots: {
        content: 'base-mode bg-(--ui-color-bg-content-primary) shadow-(--popup-window-box-shadow) rounded-(--ui-border-radius-xl) will-change-[opacity] motion-safe:data-[state=open]:animate-[scale-in_100ms_ease-out] motion-safe:data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-dropdown-menu-content-transform-origin) font-[family-name:var(--ui-font-family-primary)] relative isolate px-0 py-(--menu-popup-padding) pointer-events-auto',
        viewport: 'relative w-full max-h-[40vh] min-w-[192px] max-w-[240px] overflow-x-hidden overflow-y-auto scrollbar-thin',
        group: 'grid',
        label: 'w-full h-(--popup-window-delimiter-section-height) px-[18px] mt-(--menu-item-block-stack-space) flex flex-row rtl:flex-row-reverse items-center select-none outline-none whitespace-nowrap text-start text-(length:--ui-size-sm) text-(--b24ui-typography-legend-color) font-(--ui-font-weight-normal) after:ms-[10px] after:block after:flex-1 after:min-w-[15px] after:h-px after:bg-(--ui-color-divider-vibrant-default)',
        item: 'group w-full h-[36px] px-[18px] mt-(--menu-item-block-stack-space) relative flex flex-row rtl:flex-row-reverse items-center select-none outline-none whitespace-nowrap cursor-pointer data-disabled:cursor-not-allowed data-disabled:opacity-30 text-start text-(length:--ui-font-size-md) text-(--b24ui-typography-legend-color) hover:text-(--b24ui-typography-label-color) data-highlighted:text-(--b24ui-typography-label-color) data-[state=open]:text-(--b24ui-typography-label-color) hover:bg-(--ui-color-divider-optical-1-alt) data-highlighted:bg-(--ui-color-divider-optical-1-alt) data-[state=open]:bg-(--ui-color-divider-optical-1-alt) transition-colors',
        itemLeadingIcon: 'shrink-0 size-[18px] text-(--ui-color-design-plain-content-icon-secondary) group-data-highlighted:text-(--ui-color-accent-main-primary) group-data-[state=open]:text-(--ui-color-accent-main-primary) group-data-[state=checked]:text-(--ui-color-accent-main-primary) transition-colors',
        itemLeadingAvatar: 'shrink-0 size-[16px] me-[8px]',
        itemLeadingAvatarSize: '2xs',
        itemWrapper: 'ms-[4px] flex-1 flex flex-col text-start min-w-0',
        itemLabel: 'max-w-[240px] truncate -mt-px group-data-[state=checked]:text-(--ui-color-accent-main-primary)',
        itemDescription: 'max-w-[240px] truncate -mt-[6px] text-(--b24ui-typography-description-color) text-(length:--ui-font-size-sm)',
        itemLabelExternalIcon: 'inline-block size-[16px] text-(--ui-color-design-plain-content-icon-secondary)'
      },
      variants: {
        active: {
          true: {
            item: 'text-(--ui-color-accent-main-primary) hover:text-(--ui-color-accent-main-primary)',
            itemLeadingIcon: 'text-(--ui-color-accent-main-primary) hover:text-(--ui-color-accent-main-primary) group-data-[state=open]:text-(--ui-color-accent-main-primary)'
          },
          false: {}
        }
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import bitrix24UIPluginVite from '@bitrix24/b24ui-nuxt/vite'

export default defineConfig({
  plugins: [
    vue(),
    bitrix24UIPluginVite({
      b24ui: {
        editorSuggestionMenu: {
          slots: {
            content: 'base-mode bg-(--ui-color-bg-content-primary) shadow-(--popup-window-box-shadow) rounded-(--ui-border-radius-xl) will-change-[opacity] motion-safe:data-[state=open]:animate-[scale-in_100ms_ease-out] motion-safe:data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-dropdown-menu-content-transform-origin) font-[family-name:var(--ui-font-family-primary)] relative isolate px-0 py-(--menu-popup-padding) pointer-events-auto',
            viewport: 'relative w-full max-h-[40vh] min-w-[192px] max-w-[240px] overflow-x-hidden overflow-y-auto scrollbar-thin',
            group: 'grid',
            label: 'w-full h-(--popup-window-delimiter-section-height) px-[18px] mt-(--menu-item-block-stack-space) flex flex-row rtl:flex-row-reverse items-center select-none outline-none whitespace-nowrap text-start text-(length:--ui-size-sm) text-(--b24ui-typography-legend-color) font-(--ui-font-weight-normal) after:ms-[10px] after:block after:flex-1 after:min-w-[15px] after:h-px after:bg-(--ui-color-divider-vibrant-default)',
            item: 'group w-full h-[36px] px-[18px] mt-(--menu-item-block-stack-space) relative flex flex-row rtl:flex-row-reverse items-center select-none outline-none whitespace-nowrap cursor-pointer data-disabled:cursor-not-allowed data-disabled:opacity-30 text-start text-(length:--ui-font-size-md) text-(--b24ui-typography-legend-color) hover:text-(--b24ui-typography-label-color) data-highlighted:text-(--b24ui-typography-label-color) data-[state=open]:text-(--b24ui-typography-label-color) hover:bg-(--ui-color-divider-optical-1-alt) data-highlighted:bg-(--ui-color-divider-optical-1-alt) data-[state=open]:bg-(--ui-color-divider-optical-1-alt) transition-colors',
            itemLeadingIcon: 'shrink-0 size-[18px] text-(--ui-color-design-plain-content-icon-secondary) group-data-highlighted:text-(--ui-color-accent-main-primary) group-data-[state=open]:text-(--ui-color-accent-main-primary) group-data-[state=checked]:text-(--ui-color-accent-main-primary) transition-colors',
            itemLeadingAvatar: 'shrink-0 size-[16px] me-[8px]',
            itemLeadingAvatarSize: '2xs',
            itemWrapper: 'ms-[4px] flex-1 flex flex-col text-start min-w-0',
            itemLabel: 'max-w-[240px] truncate -mt-px group-data-[state=checked]:text-(--ui-color-accent-main-primary)',
            itemDescription: 'max-w-[240px] truncate -mt-[6px] text-(--b24ui-typography-description-color) text-(length:--ui-font-size-sm)',
            itemLabelExternalIcon: 'inline-block size-[16px] text-(--ui-color-design-plain-content-icon-secondary)'
          },
          variants: {
            active: {
              true: {
                item: 'text-(--ui-color-accent-main-primary) hover:text-(--ui-color-accent-main-primary)',
                itemLeadingIcon: 'text-(--ui-color-accent-main-primary) hover:text-(--ui-color-accent-main-primary) group-data-[state=open]:text-(--ui-color-accent-main-primary)'
              },
              false: {}
            }
          }
        }
      }
    })
  ]
})

EditorMentionMenu

A user mention suggestion menu that automatically appears upon typing the at sign @ character in the editor.

EditorToolbar

A customizable toolbar for editor actions with multiple display modes: fixed, bubble, or floating.

On this page

  • Usage
    • Items
    • Char
    • Options
  • API
    • Props
  • Theme
Releases
Published under MIT License.

Copyright © 2024-present Bitrix24