Usage
Use anything you like in the default slot of the ContextMenu, and right-click on it to display the menu.
<script setup lang="ts">
const items = ref([
[
{
label: 'Appearance',
children: [
{
label: 'System'
},
{
label: 'Light'
},
{
label: 'Dark'
}
]
}
],
[
{
label: 'Show Sidebar',
kbds: ['meta', 's']
},
{
label: 'Show Toolbar',
kbds: ['shift', 'meta', 'd']
},
{
label: 'Collapse Pinned Tabs',
disabled: true
}
],
[
{
label: 'Refresh the Page'
},
{
label: 'Clear Cookies and Refresh'
},
{
label: 'Clear Cache and Refresh'
},
{
type: 'separator'
},
{
label: 'Developer',
children: [
[
{
label: 'View Source',
kbds: ['meta', 'shift', 'u']
},
{
label: 'Developer Tools',
kbds: ['option', 'meta', 'i']
},
{
label: 'Inspect Elements',
kbds: ['option', 'meta', 'c']
}
],
[
{
label: 'JavaScript Console',
kbds: ['option', 'meta', 'j']
}
]
]
}
]
])
</script>
<template>
<B24ContextMenu :items="items">
<div
class="flex items-center justify-center rounded-md border border-dashed border-(--ui-color-design-outline-na-stroke) bg-(--ui-color-bg-content-primary) text-(length:--ui-font-size-sm) aspect-video w-72"
>
Right click here
</div>
</B24ContextMenu>
</template>
Items
Use the items prop as an array of objects with the following properties:
label?: stringicon?: IconComponentavatar?: AvatarPropskbds?: string[] | KbdProps[]type?: "link" | "label" | "separator" | "checkbox"color?: ContextMenu['color']"checked?: booleandisabled?: booleanslot?: stringonSelect?: (e: Event) => voidonUpdateChecked?: (checked: boolean) => voidchildren?: ContextMenuItem[] | ContextMenuItem[][]class?: anyb24ui?: { item?: ClassNameValue, label?: ClassNameValue, separator?: ClassNameValue, itemLeadingIcon?: ClassNameValue, itemLeadingAvatarSize?: ClassNameValue, itemLeadingAvatar?: ClassNameValue, itemLabel?: ClassNameValue, itemLabelExternalIcon?: ClassNameValue, itemTrailing?: ClassNameValue, itemTrailingIcon?: ClassNameValue, itemTrailingKbds?: ClassNameValue, itemTrailingKbdsSize?: ClassNameValue }
You can pass any property from the Link component such as to, target, etc.
<script setup lang="ts">
import type { ContextMenuItem } from '@bitrix24/b24ui-nuxt'
const items = ref<ContextMenuItem[][]>([
[
{
label: 'Appearance',
children: [
{
label: 'System'
},
{
label: 'Light'
},
{
label: 'Dark'
}
]
}
],
[
{
label: 'Show Sidebar',
kbds: ['meta', 's']
},
{
label: 'Show Toolbar',
kbds: ['shift', 'meta', 'd']
},
{
label: 'Collapse Pinned Tabs',
disabled: true
}
],
[
{
label: 'Refresh the Page'
},
{
label: 'Clear Cookies and Refresh'
},
{
label: 'Clear Cache and Refresh'
},
{
type: 'separator'
},
{
label: 'Developer',
children: [
[
{
label: 'View Source',
kbds: ['meta', 'shift', 'u']
},
{
label: 'Developer Tools',
kbds: ['option', 'meta', 'i']
},
{
label: 'Inspect Elements',
kbds: ['option', 'meta', 'c']
}
],
[
{
label: 'JavaScript Console',
kbds: ['option', 'meta', 'j']
}
]
]
}
]
])
</script>
<template>
<B24ContextMenu :items="items">
<div
class="flex items-center justify-center rounded-md border border-dashed border-(--ui-color-design-outline-na-stroke) text-(length:--ui-font-size-sm) bg-(--ui-color-bg-content-primary) aspect-video w-72"
>
Right click here
</div>
</B24ContextMenu>
</template>
items prop to create separated groups of items.children array of objects with the same properties as the items prop to create a nested menu which can be controlled using the open, defaultOpen and content properties.Modal
Use the modal prop to control whether the ContextMenu blocks interaction with outside content. Defaults to true.
<script setup lang="ts">
import type { ContextMenuItem } from '@bitrix24/b24ui-nuxt'
const items = ref<ContextMenuItem[]>([
{
label: 'System'
},
{
label: 'Light'
},
{
label: 'Dark'
}
])
</script>
<template>
<B24ContextMenu
:modal="false"
:items="items"
:b24ui="{
content: 'w-48'
}"
>
<div
class="flex items-center justify-center rounded-md border border-dashed border-(--ui-color-design-outline-na-stroke) text-(length:--ui-font-size-sm) bg-(--ui-color-bg-content-primary) aspect-video w-72"
>
Right click here
</div>
</B24ContextMenu>
</template>
Disabled
Use the disabled prop to disable the ContextMenu.
<script setup lang="ts">
import type { ContextMenuItem } from '@bitrix24/b24ui-nuxt'
const items = ref<ContextMenuItem[]>([
{
label: 'System'
},
{
label: 'Light'
},
{
label: 'Dark'
}
])
</script>
<template>
<B24ContextMenu disabled :items="items">
<div
class="flex items-center justify-center rounded-md border border-dashed border-(--ui-color-design-outline-na-stroke) text-(length:--ui-font-size-sm) bg-(--ui-color-bg-content-primary) aspect-video w-72"
>
Right click here
</div>
</B24ContextMenu>
</template>
Examples
With checkbox items
You can use the type property with checkbox and use the checked / onUpdateChecked properties to control the checked state of the item.
checked state of items, it's recommended to wrap your items array inside a computed.With color items
You can use the color property to highlight certain items with a color.
With custom slot
Use the slot property to customize a specific item.
You will have access to the following slots:
#{{ item.slot }}#{{ item.slot }}-leading#{{ item.slot }}-label#{{ item.slot }}-trailing
#item, #item-leading, #item-label and #item-trailing slots to customize all items.Extract shortcuts
Use the extractShortcuts utility to automatically define shortcuts from menu items with a kbds property. It recursively extracts shortcuts and returns an object compatible with defineShortcuts.
<script setup lang="ts">
const items = [
[{
label: 'Show Sidebar',
kbds: ['meta', 'S'],
onSelect() {
console.log('Show Sidebar clicked')
}
}, {
label: 'Show Toolbar',
kbds: ['shift', 'meta', 'D'],
onSelect() {
console.log('Show Toolbar clicked')
}
}, {
label: 'Collapse Pinned Tabs',
disabled: true
}], [{
label: 'Refresh the Page'
}, {
label: 'Clear Cookies and Refresh'
}, {
label: 'Clear Cache and Refresh'
}, {
type: 'separator' as const
}, {
label: 'Developer',
children: [[{
label: 'View Source',
kbds: ['option', 'meta', 'U'],
onSelect() {
console.log('View Source clicked')
}
}, {
label: 'Developer Tools',
kbds: ['option', 'meta', 'I'],
onSelect() {
console.log('Developer Tools clicked')
}
}], [{
label: 'Inspect Elements',
kbds: ['option', 'meta', 'C'],
onSelect() {
console.log('Inspect Elements clicked')
}
}], [{
label: 'JavaScript Console',
kbds: ['option', 'meta', 'J'],
onSelect() {
console.log('JavaScript Console clicked')
}
}]]
}]
]
defineShortcuts(extractShortcuts(items))
</script>
select function of the corresponding item.API
Props
Slots
Emits
Theme
export default defineAppConfig({
b24ui: {
contextMenu: {
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-context-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] scroll-py-1 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)',
separator: 'my-[8px] mx-[18px] h-[1px] 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-[12px]',
itemLeadingAvatarSize: '2xs',
itemTrailing: 'ml-auto rtl:ml-0 rtl:mr-auto inline-flex gap-1.5 items-center',
itemTrailingIcon: 'shrink-0 size-[24px] text-(--ui-color-accent-main-primary)',
itemTrailingKbds: 'shrink-0 hidden lg:inline-flex items-center gap-0.5',
itemWrapper: 'flex-1 flex flex-col text-start min-w-0',
itemTrailingKbdsSize: 'md',
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: {
color: {
'air-primary': {
item: 'style-filled'
},
'air-primary-success': {
item: 'style-filled-success'
},
'air-primary-alert': {
item: 'style-filled-alert'
},
'air-primary-copilot': {
item: 'style-filled-copilot'
},
'air-primary-warning': {
item: 'style-filled-warning'
}
},
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: {}
},
loading: {
true: {
itemLeadingIcon: 'animate-spin'
}
}
},
compoundVariants: [],
defaultVariants: {}
}
}
})
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import bitrix24UIPluginVite from '@bitrix24/b24ui-nuxt/vite'
export default defineConfig({
plugins: [
vue(),
bitrix24UIPluginVite({
b24ui: {
contextMenu: {
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-context-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] scroll-py-1 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)',
separator: 'my-[8px] mx-[18px] h-[1px] 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-[12px]',
itemLeadingAvatarSize: '2xs',
itemTrailing: 'ml-auto rtl:ml-0 rtl:mr-auto inline-flex gap-1.5 items-center',
itemTrailingIcon: 'shrink-0 size-[24px] text-(--ui-color-accent-main-primary)',
itemTrailingKbds: 'shrink-0 hidden lg:inline-flex items-center gap-0.5',
itemWrapper: 'flex-1 flex flex-col text-start min-w-0',
itemTrailingKbdsSize: 'md',
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: {
color: {
'air-primary': {
item: 'style-filled'
},
'air-primary-success': {
item: 'style-filled-success'
},
'air-primary-alert': {
item: 'style-filled-alert'
},
'air-primary-copilot': {
item: 'style-filled-copilot'
},
'air-primary-warning': {
item: 'style-filled-warning'
}
},
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: {}
},
loading: {
true: {
itemLeadingIcon: 'animate-spin'
}
}
},
compoundVariants: [],
defaultVariants: {}
}
}
})
]
})