Overlays

ContextMenu

Display a menu that appears on right click.

Usage

Right click here
<script setup>
const { x, y } = useMouse()
const { y: windowY } = useWindowScroll()

const isOpen = ref(false)
const virtualElement = ref({ getBoundingClientRect: () => ({}) })

function onContextMenu () {
  const top = unref(y) - unref(windowY)
  const left = unref(x)

  virtualElement.value.getBoundingClientRect = () => ({
    width: 0,
    height: 0,
    top,
    left
  })

  isOpen.value = true
}
</script>

<template>
  <div class="w-full" @contextmenu.prevent="onContextMenu">
    <Placeholder class="h-96 select-none w-full flex items-center justify-center">
      Right click here
    </Placeholder>

    <UContextMenu v-model="isOpen" :virtual-element="virtualElement">
      <div class="p-4">
        Menu
      </div>
    </UContextMenu>
  </div>
</template>

Popper

Use the popper prop to customize the popper instance.

Arrow

Right click here
<script setup>
const { x, y } = useMouse()
const { y: windowY } = useWindowScroll()

const isOpen = ref(false)
const virtualElement = ref({ getBoundingClientRect: () => ({}) })

function onContextMenu () {
  const top = unref(y) - unref(windowY)
  const left = unref(x)

  virtualElement.value.getBoundingClientRect = () => ({
    width: 0,
    height: 0,
    top,
    left
  })

  isOpen.value = true
}
</script>

<template>
  <div class="w-full" @contextmenu.prevent="onContextMenu">
    <Placeholder class="h-96 select-none w-full flex items-center justify-center">
      Right click here
    </Placeholder>

    <UContextMenu v-model="isOpen" :virtual-element="virtualElement" :popper="{ arrow: true, placement: 'right' }">
      <div class="p-4">
        Menu
      </div>
    </UContextMenu>
  </div>
</template>

Placement

Right click here
<script setup>
const { x, y } = useMouse()
const { y: windowY } = useWindowScroll()

const isOpen = ref(false)
const virtualElement = ref({ getBoundingClientRect: () => ({}) })

function onContextMenu () {
  const top = unref(y) - unref(windowY)
  const left = unref(x)

  virtualElement.value.getBoundingClientRect = () => ({
    width: 0,
    height: 0,
    top,
    left
  })

  isOpen.value = true
}
</script>

<template>
  <div class="w-full" @contextmenu.prevent="onContextMenu">
    <Placeholder class="h-96 select-none w-full flex items-center justify-center">
      Right click here
    </Placeholder>

    <UContextMenu v-model="isOpen" :virtual-element="virtualElement" :popper="{ placement: 'right-start' }">
      <div class="p-4">
        Menu
      </div>
    </UContextMenu>
  </div>
</template>

Offset

Right click here
<script setup>
const { x, y } = useMouse()
const { y: windowY } = useWindowScroll()

const isOpen = ref(false)
const virtualElement = ref({ getBoundingClientRect: () => ({}) })

function onContextMenu () {
  const top = unref(y) - unref(windowY)
  const left = unref(x)

  virtualElement.value.getBoundingClientRect = () => ({
    width: 0,
    height: 0,
    top,
    left
  })

  isOpen.value = true
}
</script>

<template>
  <div class="w-full" @contextmenu.prevent="onContextMenu">
    <Placeholder class="h-96 select-none w-full flex items-center justify-center">
      Right click here
    </Placeholder>

    <UContextMenu v-model="isOpen" :virtual-element="virtualElement" :popper="{ offset: 0 }">
      <div class="p-4">
        Menu
      </div>
    </UContextMenu>
  </div>
</template>

Props

virtualElementrequired
Record<string, any>
ui
any
undefined
popper
{}
{}
modelValue
boolean
false

Config

UContextMenu.vue
{
  "wrapper": "relative",
  "container": "z-20 group",
  "width": "",
  "background": "bg-white dark:bg-gray-900",
  "shadow": "shadow-lg",
  "rounded": "rounded-md",
  "ring": "ring-1 ring-gray-200 dark:ring-gray-800",
  "base": "overflow-hidden focus:outline-none relative",
  "transition": {
    "enterActiveClass": "transition ease-out duration-200",
    "enterFromClass": "opacity-0 translate-y-1",
    "enterToClass": "opacity-100 translate-y-0",
    "leaveActiveClass": "transition ease-in duration-150",
    "leaveFromClass": "opacity-100 translate-y-0",
    "leaveToClass": "opacity-0 translate-y-1"
  },
  "popper": {
    "placement": "bottom-start",
    "scroll": false
  },
  "arrow": {
    "base": "before:w-2 before:h-2",
    "ring": "before:ring-1 before:ring-gray-200 dark:before:ring-gray-800",
    "rounded": "before:rounded-sm",
    "background": "before:bg-gray-200 dark:before:bg-gray-800",
    "shadow": "before:shadow",
    "placement": "group-data-[popper-placement*=\"right\"]:-left-1 group-data-[popper-placement*=\"left\"]:-right-1 group-data-[popper-placement*=\"top\"]:-bottom-1 group-data-[popper-placement*=\"bottom\"]:-top-1"
  }
}