Custom Right Click Context Menu in Vue3

Selena J
Fortitude Technologies
4 min readNov 3, 2023
Photo by Clément Hélardot on Unsplash

In this article, we’ll walk through the steps to achieve a custom right click menu using Vue 3 and the Composition API. We’ll create a main component with a “Users” table, and on right-clicking a row, we’ll suppress the browser’s default context menu and display a custom menu. The custom context menu will accept a list of actions as props and emit an event back to the source component to indicate which action was clicked.

UserTable

In the UserTable.vue component, we create a Users table with a right-click event handler. When the user right-clicks a row, we suppress the default context menu, display the custom context menu, and emit an event when an action is clicked.

Here’s a breakdown of the code:

  • We define the users data using ref to store user information.
  • showMenu is a reactive variable that controls the visibility of the custom context menu.
  • menuX and menuY store the coordinates for the context menu's position.
  • contextMenuActions defines the actions to be made available in the context menu.
  • contextMenu.prevent defines the action we want to take that overrides the default behavior of a right click. Adding this to the table row is what allows us to create our own behavior on right click of the element.
  • showContextMenu is called when a row is right-clicked. It sets the position and shows the custom context menu. Here we get the mouseClick event properties and pass in the specific row that was clicked on.
  • closeContextMenu is used to close the custom context menu and is triggered by clicking the overlay. The overlay is at z-49 so it will show above our table, but below the custom contextMenu which sits at z-50
  • handleActionClick handles action clicks in the context menu and logs the chosen action and the corresponding row. This is where you will implement your own actions to be taken on the selected row.
  • The overlay class only appears while the contextMenu is open, hence the v-if="showMenu"
//UserTable.vue
<template>
<div>
<table>
<thead>
<tr>
<th>User ID</th>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<tr
v-for="user in users"
:key="user.id"
@contextmenu.prevent="showContextMenu($event, user)"
>
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
</tr>
</tbody>
</table>

<!-- Overlay to close the menu -->
<div class="overlay" @click="closeContextMenu" v-if="showMenu" />

<!-- Custom Context Menu -->
<ContextMenu
v-if="showMenu"
:actions="contextMenuActions"
@action-clicked="handleActionClick"
:x="menuX"
:y="menuY"
/>
</div>
</template>

<script setup>
import { ref } from 'vue';
import ContextMenu from '@/components/Menus/ContextMenu.vue';

const users = ref([
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
{ id: 3, name: 'Charlie', email: 'charlie@example.com' },
]);

const showMenu = ref(false);
const menuX = ref(0);
const menuY = ref(0);
const targetRow = ref({});
const contextMenuActions = ref([
{ label: 'Edit', action: 'edit' },
{ label: 'Delete', action: 'delete' },
]);

const showContextMenu = (event, user) => {
event.preventDefault();
showMenu.value = true;
targetRow.value = user;
menuX.value = event.clientX;
menuY.value = event.clientY;
};

const closeContextMenu = () => {
showMenu.value = false;
};

function handleActionClick(action){
console.log(action);
console.log(targetRow.value);
}
</script>

<style scoped>
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: transparent;
z-index: 49;
}

.overlay::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
}

.overlay:hover {
cursor: pointer;
}
</style>

ContextMenu

In ContextMenu.vue, we create a custom context menu component that accepts actions as props and emits an event when an action is clicked. It also uses CSS to style the menu.

Here’s a breakdown of the code:

  • actions, x, and y are defined using defineProps to receive from the parent component (UserTable.vue).
  • emit is used to define the event action-clicked to emit when a menu item is clicked back to the parent component for handling.
  • emitAction is a function called when a menu item is clicked. It emits the chosen action to the parent component.
  • By accepting a list of actions we are able to reuse the contextMenu in other areas of the application with different desired measures.
//ContextMenu.vue
<template>
<div
class="fixed h-1/3 z-50 context-menu"
:style="{ top: y + 'px', left: x + 'px' }"
>
<div
v-for="action in actions"
:key="action.action"
@click="emitAction(action.action)"
>
{{ action.label }}
</div>
</div>
</template>

<script setup>
import { ref, defineProps, defineEmits } from 'vue';

const { actions, x, y } = defineProps(['actions', 'x', 'y']);
const emit = defineEmits(['action-clicked']);

const emitAction = (action) => {
emit('action-clicked', action);
};
</script>

<style scoped>
.context-menu {
position: absolute;
background: white;
border: 1px solid #ccc;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
min-width: 150px;
}

.context-menu div {
padding: 10px;
cursor: pointer;
}

.context-menu div:hover {
background-color: #f0f0f0;
}
</style>

In summary, these components work together to create a custom right-click menu in a Vue 3 application. The UserTable.vue component manages user data and handles the right-click event, while the ContextMenu.vue component displays the custom context menu and communicates with its parent component. The overlay provides a way to close the context menu by clicking outside of it, improving the user experience.

--

--

Selena J
Fortitude Technologies

Hey there :) I am a recent graduate from UVA and currently a Software Engineer working in full stack development. Join me as I share my tips and experiences!