Context menu
The Context menu component opens a menu at the cursor when the user right-clicks its content. It is positioned at the pointer, traps focus while open, supports arrow-key navigation through the menu, and closes on Escape, scroll, or an outside click.
Nest a Menu flyout inside the menu to add submenus. A prediction cone keeps a submenu open while the pointer moves diagonally towards it — and back out to its opener — even when the cursor briefly crosses another item. Set debug-cone to visualise that cone.
Props
debug-cone?: boolean
Visualises the submenu prediction cone for debugging. Draws the safe triangle and the pointer.
disabled?: boolean
Whether the context menu is disabled.
is-persistent?: boolean
When enabled, the context menu stays open after a menu item is clicked instead of closing. Defaults to false; individual items can also opt in via their own is-persistent.
position?: "bottom-left" | "bottom-right" | "top-left" | "…"
The position of the menu relative to the cursor.
Default: bottom-left
Emits
open: [MouseEvent]
Triggered when the menu opens, with the originating event.
close: []
Triggered when the menu closes.
Slots
default
The region that responds to right-clicks.
menu
The menu content. Receives a close function. Place a Menu with items here.
Examples
Basic
A basic context menu.
<template>
<FluxContextMenu>
<FluxPane style="padding: 36px; text-align: center">
Right-click here
</FluxPane>
<template #menu>
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
label="Cut"
type="button"/>
<FluxMenuItem
label="Copy"
type="button"/>
<FluxMenuItem
label="Paste"
type="button"/>
</FluxMenuGroup>
</FluxMenu>
</template>
</FluxContextMenu>
</template>
<script
setup
lang="ts">
import { FluxContextMenu, FluxMenu, FluxMenuGroup, FluxMenuItem, FluxPane } from '@flux-ui/components';
</script>With icons
A context menu with icons.
<template>
<FluxContextMenu @open="onOpen">
<FluxPane style="padding: 36px; text-align: center">
Right-click for actions
</FluxPane>
<template #menu="{close}">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="pen"
label="Edit"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="copy"
label="Duplicate"
type="button"
@click="close"/>
</FluxMenuGroup>
<FluxSeparator/>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="trash"
label="Delete"
type="button"
@click="close"/>
</FluxMenuGroup>
</FluxMenu>
</template>
</FluxContextMenu>
</template>
<script
setup
lang="ts">
import { FluxContextMenu, FluxMenu, FluxMenuGroup, FluxMenuItem, FluxPane, FluxSeparator } from '@flux-ui/components';
function onOpen(): void {
// The menu opened at the cursor.
}
</script>With submenus
Nest a Menu flyout to add a submenu that opens to the side.
<template>
<div style="display: flex; flex-flow: column; gap: 15px">
<div style="display: flex; align-items: center; gap: 9px">
<FluxToggle
v-model="showCone"/>
Prediction cone
</div>
<FluxContextMenu :debug-cone="showCone">
<FluxPane style="padding: 36px; text-align: center">
Right-click here
</FluxPane>
<template #menu="{close}">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="pen"
label="Rename"
type="button"
@click="close"/>
<FluxMenuFlyout
icon="arrow-up-from-square"
label="Share">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="paper-plane"
label="Email"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="copy"
label="Copy link"
type="button"
@click="close"/>
</FluxMenuGroup>
</FluxMenu>
</FluxMenuFlyout>
</FluxMenuGroup>
<FluxSeparator/>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="trash"
is-destructive
label="Delete"
type="button"
@click="close"/>
</FluxMenuGroup>
</FluxMenu>
</template>
</FluxContextMenu>
</div>
</template>
<script
lang="ts"
setup>
import { FluxContextMenu, FluxMenu, FluxMenuFlyout, FluxMenuGroup, FluxMenuItem, FluxPane, FluxSeparator, FluxToggle } from '@flux-ui/components';
import { ref } from 'vue';
const showCone = ref(false);
</script>Deep submenus
Menu flyouts nest arbitrarily deep. The prediction cone guides the pointer into a submenu and back out to its opener across every level.
<template>
<div style="display: flex; flex-flow: column; gap: 15px">
<div style="display: flex; align-items: center; gap: 9px">
<FluxToggle
v-model="showCone"/>
Prediction cone
</div>
<FluxContextMenu :debug-cone="showCone">
<FluxPane style="padding: 36px; text-align: center">
Right-click this file
</FluxPane>
<template #menu="{close}">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="arrow-up-right-from-square"
label="Open"
type="button"
@click="close"/>
<FluxMenuFlyout
icon="folder-tree"
label="Move to">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuFlyout
icon="folder"
label="Documents">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuFlyout
icon="folder"
label="Work">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="folder"
label="2025"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="folder"
label="2026"
type="button"
@click="close"/>
</FluxMenuGroup>
</FluxMenu>
</FluxMenuFlyout>
<FluxMenuItem
icon-leading="folder"
label="Personal"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="folder"
label="Invoices"
type="button"
@click="close"/>
</FluxMenuGroup>
</FluxMenu>
</FluxMenuFlyout>
<FluxMenuFlyout
icon="folder"
label="Pictures">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="folder"
label="Camera Roll"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="folder"
label="Screenshots"
type="button"
@click="close"/>
</FluxMenuGroup>
</FluxMenu>
</FluxMenuFlyout>
<FluxMenuItem
icon-leading="folder"
label="Desktop"
type="button"
@click="close"/>
</FluxMenuGroup>
</FluxMenu>
</FluxMenuFlyout>
</FluxMenuGroup>
<FluxSeparator/>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="trash"
is-destructive
label="Delete"
type="button"
@click="close"/>
</FluxMenuGroup>
</FluxMenu>
</template>
</FluxContextMenu>
</div>
</template>
<script
lang="ts"
setup>
import { FluxContextMenu, FluxMenu, FluxMenuFlyout, FluxMenuGroup, FluxMenuItem, FluxPane, FluxSeparator, FluxToggle } from '@flux-ui/components';
import { ref } from 'vue';
const showCone = ref(false);
</script>Nested formatting
A spreadsheet-style menu mixing several openers with two and three levels of nesting.
<template>
<div style="display: flex; flex-flow: column; gap: 15px">
<div style="display: flex; align-items: center; gap: 9px">
<FluxToggle
v-model="showCone"/>
Prediction cone
</div>
<FluxContextMenu :debug-cone="showCone">
<FluxPane style="padding: 36px; text-align: center">
Right-click this cell
</FluxPane>
<template #menu="{close}">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="scissors"
label="Cut"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="copy"
label="Copy"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="paste"
label="Paste"
type="button"
@click="close"/>
</FluxMenuGroup>
<FluxSeparator/>
<FluxMenuGroup>
<FluxMenuFlyout
icon="plus"
label="Insert">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="arrow-up"
label="Row above"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="arrow-down"
label="Row below"
type="button"
@click="close"/>
</FluxMenuGroup>
<FluxSeparator/>
<FluxMenuGroup>
<FluxMenuFlyout
icon="table-cells"
label="Cells">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="arrow-right"
label="Shift right"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="arrow-down"
label="Shift down"
type="button"
@click="close"/>
</FluxMenuGroup>
</FluxMenu>
</FluxMenuFlyout>
</FluxMenuGroup>
</FluxMenu>
</FluxMenuFlyout>
<FluxMenuFlyout
icon="font"
label="Format">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuFlyout
icon="hashtag"
label="Number">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
label="Currency"
type="button"
@click="close"/>
<FluxMenuItem
label="Percent"
type="button"
@click="close"/>
<FluxMenuItem
label="Date"
type="button"
@click="close"/>
</FluxMenuGroup>
</FluxMenu>
</FluxMenuFlyout>
<FluxMenuFlyout
icon="text-height"
label="Text">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="bold"
label="Bold"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="italic"
label="Italic"
type="button"
@click="close"/>
</FluxMenuGroup>
<FluxSeparator/>
<FluxMenuGroup>
<FluxMenuFlyout
icon="palette"
label="Color">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="circle"
label="Red"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="circle"
label="Green"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="circle"
label="Blue"
type="button"
@click="close"/>
</FluxMenuGroup>
</FluxMenu>
</FluxMenuFlyout>
</FluxMenuGroup>
</FluxMenu>
</FluxMenuFlyout>
</FluxMenuGroup>
</FluxMenu>
</FluxMenuFlyout>
</FluxMenuGroup>
<FluxSeparator/>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="trash"
is-destructive
label="Delete row"
type="button"
@click="close"/>
</FluxMenuGroup>
</FluxMenu>
</template>
</FluxContextMenu>
</div>
</template>
<script
lang="ts"
setup>
import { FluxContextMenu, FluxMenu, FluxMenuFlyout, FluxMenuGroup, FluxMenuItem, FluxPane, FluxSeparator, FluxToggle } from '@flux-ui/components';
import { ref } from 'vue';
const showCone = ref(false);
</script>Real-world
A Finder-style menu with several submenu openers stacked under each other.
<template>
<div style="display: flex; flex-flow: column; gap: 15px">
<div style="display: flex; align-items: center; gap: 9px">
<FluxToggle
v-model="showCone"/>
Prediction cone
</div>
<FluxContextMenu :debug-cone="showCone">
<FluxPane style="padding: 36px; text-align: center">
Right-click this item
</FluxPane>
<template #menu="{close}">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="arrow-up-right-from-square"
label="Open"
type="button"
@click="close"/>
<FluxMenuFlyout
icon="image"
label="Open With">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="eye"
label="Preview"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="image"
label="Photos"
type="button"
@click="close"/>
</FluxMenuGroup>
<FluxSeparator/>
<FluxMenuGroup>
<FluxMenuItem
label="App Store…"
type="button"
@click="close"/>
</FluxMenuGroup>
</FluxMenu>
</FluxMenuFlyout>
<FluxMenuItem
icon-leading="trash"
is-destructive
label="Move to Trash"
type="button"
@click="close"/>
</FluxMenuGroup>
<FluxSeparator/>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="circle-info"
label="Get Info"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="pen"
label="Rename"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="clone"
label="Duplicate"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="eye"
label="Quick Look"
type="button"
@click="close"/>
</FluxMenuGroup>
<FluxSeparator/>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="copy"
label="Copy"
type="button"
@click="close"/>
<FluxMenuFlyout
icon="arrow-up-from-square"
label="Share">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="paper-plane"
label="Mail"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="copy"
label="Copy Link"
type="button"
@click="close"/>
</FluxMenuGroup>
</FluxMenu>
</FluxMenuFlyout>
<FluxMenuFlyout
icon="bolt"
label="Quick Actions">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="rotate-left"
label="Rotate Left"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="pen"
label="Markup"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="image"
label="Convert Image"
type="button"
@click="close"/>
</FluxMenuGroup>
</FluxMenu>
</FluxMenuFlyout>
</FluxMenuGroup>
<FluxSeparator/>
<FluxMenuGroup>
<FluxMenuFlyout
icon="palette"
label="Tags">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
icon-leading="circle"
label="Red"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="circle"
label="Orange"
type="button"
@click="close"/>
<FluxMenuItem
icon-leading="circle"
label="Green"
type="button"
@click="close"/>
</FluxMenuGroup>
</FluxMenu>
</FluxMenuFlyout>
<FluxMenuFlyout
icon="gear"
label="Services">
<FluxMenu>
<FluxMenuGroup>
<FluxMenuItem
label="Show in Enclosing Folder"
type="button"
@click="close"/>
<FluxMenuItem
label="New Terminal at Folder"
type="button"
@click="close"/>
</FluxMenuGroup>
</FluxMenu>
</FluxMenuFlyout>
</FluxMenuGroup>
</FluxMenu>
</template>
</FluxContextMenu>
</div>
</template>
<script
lang="ts"
setup>
import { FluxContextMenu, FluxMenu, FluxMenuFlyout, FluxMenuGroup, FluxMenuItem, FluxPane, FluxSeparator, FluxToggle } from '@flux-ui/components';
import { ref } from 'vue';
const showCone = ref(false);
</script>With color picker
Embed a full component with a Menu pane. The picker stays interactive while the menu is open.
<template>
<FluxContextMenu>
<FluxPane style="padding: 36px; text-align: center">
Right-click here
</FluxPane>
<template #menu>
<FluxMenu>
<FluxMenuGroup>
<FluxMenuSubHeader label="Highlight"/>
<FluxMenuPane>
<FluxColorPicker v-model="color"/>
</FluxMenuPane>
</FluxMenuGroup>
</FluxMenu>
</template>
</FluxContextMenu>
</template>
<script
setup
lang="ts">
import { FluxColorPicker, FluxContextMenu, FluxMenu, FluxMenuGroup, FluxMenuPane, FluxMenuSubHeader, FluxPane } from '@flux-ui/components';
import { ref } from 'vue';
const color = ref('#ef4444');
</script>