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 usingref
to store user information.showMenu
is a reactive variable that controls the visibility of the custom context menu.menuX
andmenuY
store the coordinates for the context menu's position.contextMenuActions
defines the actions to be made available in the context menu.contextMenu.prevent
overrides the default behavior of a right click with the action we want to take place instead. 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 render as being above our table and below the custom contextMenu which sits at z-50handleActionClick
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 is only applied while the contextMenu is open, hence thev-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
, andy
are defined usingdefineProps
to receive from the parent component (UserTable.vue
).emit
defines the eventaction-clicked
to be emitted back to the parent component when a menu item is clicked.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 options.
//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 and improving the user experience.