| |

Vue.js 12 🟢 Key and Mouse Button Modifiers

A click is not just a click. It can be a left click, a right click, a middle click, a Ctrl+click, a Shift+click, a Meta+click. A keypress is not just a keypress. It can be Enter, Escape, Tab, an arrow key, or a key with Ctrl, Alt, or Shift held. The browser reports all of these as events, and the handler must decide which ones it cares about. Vue’s key and mouse button modifiers express these decisions declaratively. They are filters attached to the event binding, and they run before your handler. If the event does not match the filter, the handler does not run.

This chapter covers key modifiers, mouse button modifiers, and the system modifiers that combine with both. It builds on the previous chapter’s introduction to event modifiers and goes deeper into the specific case of filtering input events by the key or button that produced them. You will learn the full set of built-in key aliases, how to use arbitrary key names, how .exact tightens system modifier matching, and how these modifiers interact with the event object and with each other.

Key point: Key modifiers filter keyboard events by event.key. Mouse button modifiers filter mouse events by event.button. System modifiers (.ctrl, .alt, .shift, .meta) filter both by checking the corresponding boolean on the event. The filter runs before your handler. If the filter does not match, the handler is not invoked.


Why key and mouse modifiers exist

The filtering problem. A keydown handler attached to an input fires on every key. Most of the time, the handler only cares about a few keys: Enter to submit, Escape to cancel, arrows to navigate. Without modifiers, the handler must check event.key against a list and return early if the key is not one it handles. The check is repetitive and easy to get wrong. Key modifiers move the check into the template, where it is visible and declarative.

The key-code problem. Before event.key, the browser exposed event.keyCode and event.which. These were numeric codes with browser inconsistencies, and they were deprecated. Vue’s key modifiers were originally based on keyCode, but since Vue 3 they are based on event.key, the modern, standardized property. The built-in aliases map to the corresponding event.key values .

The custom alias problem. The built-in aliases cover the common keys, but applications need others: comma, semicolon, page up, page down, F-keys. Vue 2 allowed registering custom aliases globally through Vue.config.keyCodes. Vue 3 removed this in favor of using the kebab-case version of event.key directly. @keyup.page-down fires when event.key === 'PageDown'. @keyup.comma fires when event.key === ','. The rule is: convert the event.key value to kebab-case and use it as the modifier .

The mouse button problem. Mouse events report which button was pressed through event.button: 0 for left, 1 for middle, 2 for right. Without modifiers, a click handler must check this value to distinguish a right-click from a left-click. The .left, .right, and .middle modifiers provide the check directly.

The system modifier problem. Ctrl+click opens a link in a new tab. Ctrl+Enter submits a form. Shift+click extends a selection. These are distinct interactions that often need distinct handlers. The .ctrl, .alt, .shift, and .meta modifiers filter by the state of the corresponding key. Without them, the handler must check the modifier keys manually.

The exact problem. @click.ctrl fires whenever Ctrl is held, regardless of whether other modifier keys are also held. Sometimes the intent is stricter: only Ctrl, no Shift, no Alt, no Meta. The .exact modifier enforces this. Without it, a Ctrl+Shift+click would also trigger the Ctrl handler .


a. Key modifiers

Vue provides built-in aliases for the most common keys. Each alias maps to a value of event.key.

Modifierevent.keyKey
.enter'Enter'Enter / Return
.tab'Tab'Tab
.delete'Delete' or 'Backspace'Delete or Backspace
.esc'Escape'Escape
.space' 'Space
.up'ArrowUp'Up arrow
.down'ArrowDown'Down arrow
.left'ArrowLeft'Left arrow
.right'ArrowRight'Right arrow

The .delete alias is notable because it matches both Delete and Backspace. This is a deliberate choice: the intent is usually “the user pressed the delete key,” and both keys serve that purpose on different keyboards.

The aliases are used with keyboard events: @keyup, @keydown, @keypress.

<input @keyup.enter="submit">
<input @keyup.esc="cancel">
<input @keyup.tab="next">
<input @keyup.delete="remove">
<input @keyup.space="toggle">
<input @keyup.up="moveUp">
<input @keyup.down="moveDown">

For keys without a built-in alias, use the kebab-case version of event.key. The conversion rule: split camelCase into words, lowercase, join with hyphens.

event.keyModifier
'PageDown'.page-down
'PageUp'.page-up
'Home'.home
'End'.end
'Insert'.insert
'F1'.f1
',',. (punctuation)
';'.;

Punctuation keys are an edge case. event.key for a comma is ',', so the modifier is @keyup.,. This is valid but looks odd. An alternative is to use a method that checks event.key directly for keys that are not letters or numbers.

Key modifiers combine with system modifiers. @keyup.ctrl.enter fires when Ctrl and Enter are held together. @keyup.alt.enter fires when Alt and Enter are held together.

<input @keyup.ctrl.enter="submit">
<input @keyup.alt.enter="forceSubmit">
<input @keyup.shift.enter="newLine">

The system modifier is checked first, then the key modifier. Both must match for the handler to run.


b. Mouse button modifiers

Mouse events report the button through event.button. Vue provides three modifiers to filter by button.

Modifierevent.buttonButton
.left0Left button
.middle1Middle button
.right2Right button

The modifiers are used with mouse events: @click, @mousedown, @mouseup.

<button @click.left="onLeft">Left click</button>
<button @click.right.prevent="onRight">Right click</button>
<button @click.middle="onMiddle">Middle click</button>

The .right modifier is commonly paired with .prevent to suppress the browser’s context menu. A right-click on a custom context menu trigger needs both: .prevent suppresses the menu, .right filters the button.

<div @click.right.prevent="showContextMenu">Right-click me</div>

Without .prevent, the browser’s native context menu appears alongside the custom one. Without .right, any click triggers the custom menu.

A subtle point: @click fires for the left button by default on most elements. The .left modifier is redundant for @click in that case, but it is useful when combined with .exact or when the browser’s default behavior might differ. For @mousedown and @mouseup, all three buttons fire, so the modifiers are necessary to distinguish them.

<button @mousedown.left="startDrag">Start drag</button>
<button @mousedown.right="showMenu">Show menu</button>

Without the button modifier, both handlers would fire on any button press.


c. System modifiers and .exact

The four system modifiers filter events by the state of the modifier keys: .ctrl, .alt, .shift, .meta.

<button @click.ctrl="onCtrlClick">Ctrl+Click</button>
<button @click.alt="onAltClick">Alt+Click</button>
<button @click.shift="onShiftClick">Shift+Click</button>
<button @click.meta="onMetaClick">Cmd+Click</button>

Each modifier fires when the corresponding key is held. Other modifier keys may or may not be held. @click.ctrl fires when Ctrl is held, regardless of Shift or Alt.

The .exact modifier tightens the match. With .exact, the handler fires only when the specified system modifiers are held and no others.

<button @click.ctrl.exact="onCtrlOnly">
  Ctrl+Click, no other keys
</button>
<button @click.exact="onNoModifiers">
  Click with no modifiers
</button>

The second example, @click.exact, fires only when no modifier keys are held. It is the equivalent of “plain click.”

System modifiers combine with key modifiers and mouse button modifiers. The full chain is checked in order.

<input @keyup.ctrl.enter="submit">
<button @mousedown.ctrl.left="onCtrlLeftDrag">
<div @click.shift.right.prevent="onShiftRightClick">

In each case, all conditions must be satisfied: the correct button or key, the correct system modifiers, and any other filters.

The order of modifiers in the chain follows the convention: phase modifiers first (.capture, .once, .passive), then propagation modifiers (.stop, .prevent, .self), then button and key modifiers, then system modifiers, then .exact. This is not a hard rule, but it produces readable chains.

<!-- Conventional order -->
<button @click.stop.prevent.ctrl.exact="handler">         <!-- ✅ -->

<!-- Valid but unconventional -->
<button @click.ctrl.exact.stop.prevent="handler">         <!-- ⚠️ -->

Both work. The first reads more naturally.


Complete Example Session

<!-- ============================================ -->
<!-- PART 1: BASIC KEY MODIFIERS -->
<!-- ============================================ -->
<template>
  <input @keyup.enter="submit" placeholder="Press Enter">
  <input @keyup.esc="cancel" placeholder="Press Escape">
  <input @keyup.tab="next" placeholder="Press Tab">
</template>

<script setup>
function submit() { console.log('submitted') }
function cancel() { console.log('cancelled') }
function next() { console.log('next field') }
</script>
<!-- ============================================ -->
<!-- PART 2: ARROW KEY MODIFIERS -->
<!-- ============================================ -->
<template>
  <div @keydown.up="moveUp" @keydown.down="moveDown" tabindex="0">
    Use arrow keys
  </div>
  <input @keyup.left="moveLeft" @keyup.right="moveRight">
</template>

<script setup>
function moveUp() { console.log('up') }
function moveDown() { console.log('down') }
function moveLeft() { console.log('left') }
function moveRight() { console.log('right') }
</script>
<!-- ============================================ -->
<!-- PART 3: DELETE AND SPACE -->
<!-- ============================================ -->
<template>
  <input @keyup.delete="removeItem">
  <input @keyup.space="toggleSelection">
</template>

<script setup>
function removeItem() { console.log('removed') }
function toggleSelection() { console.log('toggled') }
</script>
<!-- ============================================ -->
<!-- PART 4: CUSTOM KEY NAMES -->
<!-- ============================================ -->
<template>
  <input @keyup.page-down="onPageDown">
  <input @keyup.page-up="onPageUp">
  <input @keyup.f1="onHelp">
  <input @keyup.home="onHome">
  <input @keyup.end="onEnd">
</template>

<script setup>
function onPageDown() { console.log('page down') }
function onPageUp() { console.log('page up') }
function onHelp() { console.log('help') }
function onHome() { console.log('home') }
function onEnd() { console.log('end') }
</script>
<!-- ============================================ -->
<!-- PART 5: SYSTEM + KEY COMBINATIONS -->
<!-- ============================================ -->
<template>
  <input @keyup.ctrl.enter="submit" placeholder="Ctrl+Enter">
  <input @keyup.alt.enter="forceSubmit" placeholder="Alt+Enter">
  <input @keyup.shift.enter="newLine" placeholder="Shift+Enter">
</template>

<script setup>
function submit() { console.log('submit') }
function forceSubmit() { console.log('force submit') }
function newLine() { console.log('new line') }
</script>
<!-- ============================================ -->
<!-- PART 6: MOUSE BUTTON MODIFIERS -->
<!-- ============================================ -->
<template>
  <button @click.left="onLeft">Left</button>
  <button @click.right.prevent="onRight">Right</button>
  <button @click.middle="onMiddle">Middle</button>
</template>

<script setup>
function onLeft() { console.log('left') }
function onRight() { console.log('right') }
function onMiddle() { console.log('middle') }
</script>
<!-- ============================================ -->
<!-- PART 7: RIGHT-CLICK CONTEXT MENU -->
<!-- ============================================ -->
<template>
  <div @click.right.prevent="showContextMenu" class="context-target">
    Right-click me
  </div>
</template>

<script setup>
function showContextMenu(event) {
  console.log('menu at', event.clientX, event.clientY)
}
</script>
<!-- ============================================ -->
<!-- PART 8: SYSTEM MODIFIERS ON CLICK -->
<!-- ============================================ -->
<template>
  <a href="/page" @click.ctrl="openInNewTab">
    Ctrl+Click to open in new tab
  </a>
  <a href="/page" @click.meta="openInNewTab">
    Cmd+Click to open in new tab
  </a>
</template>

<script setup>
function openInNewTab(event) {
  event.preventDefault()
  console.log('opening in new tab')
}
</script>
<!-- ============================================ -->
<!-- PART 9: .exact MODIFIER -->
<!-- ============================================ -->
<template>
  <button @click.ctrl.exact="onCtrlOnly">
    Ctrl only, no other modifiers
  </button>
  <button @click.exact="onPlainClick">
    Plain click, no modifiers
  </button>
  <button @click.ctrl="onCtrlAny">
    Ctrl with any other modifiers
  </button>
</template>

<script setup>
function onCtrlOnly() { console.log('ctrl only') }
function onPlainClick() { console.log('plain click') }
function onCtrlAny() { console.log('ctrl with others') }
</script>
<!-- ============================================ -->
<!-- PART 10: COMBINING ALL MODIFIER TYPES -->
<!-- ============================================ -->
<template>
  <div
    @mousedown.ctrl.left.exact="onCtrlLeftDrag"
    @click.shift.right.prevent="onShiftRightClick"
    @keyup.alt.enter="onAltEnter"
  >
    Complex modifier combinations
  </div>
</template>

<script setup>
function onCtrlLeftDrag() { console.log('ctrl + left drag') }
function onShiftRightClick() { console.log('shift + right click') }
function onAltEnter() { console.log('alt + enter') }
</script>

The ten parts covered basic key modifiers, arrow keys, delete and space, custom key names, system-key combinations, mouse button modifiers, right-click context menus, system modifiers on click, the .exact modifier, and combining all modifier types.


Quick Reference

Built-in Key Modifiers

Modifierevent.key
.enter'Enter'
.tab'Tab'
.delete'Delete' or 'Backspace'
.esc'Escape'
.space' '
.up'ArrowUp'
.down'ArrowDown'
.left'ArrowLeft'
.right'ArrowRight'

Custom Key Modifiers

event.keyModifier
'PageDown'.page-down
'PageUp'.page-up
'Home'.home
'End'.end
'Insert'.insert
'F1' through 'F12'.f1 through .f12

Mouse Button Modifiers

Modifierevent.button
.left0
.middle1
.right2

System Modifiers

ModifierKey
.ctrlControl
.altAlt
.shiftShift
.metaCommand (Mac) / Windows
.exactExcludes unspecified modifiers

Modifier Order Convention

PositionModifiers
1Phase: .capture, .once, .passive
2Propagation: .stop, .prevent, .self
3Button: .left, .middle, .right
4Key: .enter, .esc, etc.
5System: .ctrl, .alt, .shift, .meta
6Exact: .exact

Best Practices

✅ Do This:

<!-- Use key modifiers for common keys -->
<input @keyup.enter="submit">                                  <!-- ✅ -->

<!-- Use custom kebab-case for other keys -->
<input @keyup.page-down="onPageDown">                          <!-- ✅ -->

<!-- Combine system and key modifiers -->
<input @keyup.ctrl.enter="submit">                             <!-- ✅ -->

<!-- Use .right.prevent for context menus -->
<div @click.right.prevent="showMenu">                          <!-- ✅ -->

<!-- Use .exact to tighten system modifier matching -->
<button @click.ctrl.exact="onCtrlOnly">                        <!-- ✅ -->

<!-- Follow conventional modifier order -->
<button @click.stop.prevent.ctrl.exact="handler">              <!-- ✅ -->

<!-- Use .left for mousedown to distinguish buttons -->
<button @mousedown.left="startDrag">                           <!-- ✅ -->

❌ Don’t Do This:

<!-- Don't check event.key manually when modifiers exist -->
<input @keyup="(e) => e.key === 'Enter' && submit()">          <!-- ❌ -->
<input @keyup.enter="submit">                                   <!-- ✅ -->

<!-- Don't use numeric key codes -->
<input @keyup.13="submit">                                     <!-- ❌ -->

<!-- Don't forget .prevent for right-click menus -->
<div @click.right="showMenu">                                  <!-- ⚠️ -->

<!-- Don't use .exact when any modifier should work -->
<button @click.ctrl.exact="onCtrlAny">                         <!-- ⚠️ -->

<!-- Don't assume .left is needed on @click -->
<button @click.left="onClick">                                 <!-- ⚠️ redundant -->

<!-- Don't forget that .exact excludes all unspecified modifiers -->
<button @click.exact="plain">                                  <!-- ✅ -->

Common Pitfalls

PitfallWhy It HappensFix
Key modifier does not fireWrong key nameCheck event.key value
Custom key not matchingWrong kebab-caseConvert camelCase to kebab-case
.exact excludes intended keysToo strictRemove .exact
Right-click shows native menu.prevent missingAdd .prevent
.left redundant on clickDefault is leftRemove .left
System modifier fires with othersNo .exactAdd .exact
Key modifier with wrong eventUsed on @clickUse on keyboard events

Real-World Examples

1. Form Submit on Enter

<input @keyup.enter="submit">

2. Cancel on Escape

<input @keyup.esc="cancel">

3. Ctrl+Enter Submit

<textarea @keyup.ctrl.enter="submit"></textarea>

4. Arrow Key Navigation

<div @keydown.up="prev" @keydown.down="next" tabindex="0">

5. Delete Key

<li @keyup.delete="remove(item)">

6. Ctrl+Click Open in New Tab

<a href="/page" @click.ctrl="openInNewTab">

7. Right-Click Context Menu

<div @click.right.prevent="showMenu">

8. Plain Click Only

<button @click.exact="onPlainClick">

9. Shift+Click Selection

<li @click.shift="extendSelection(item)">

10. Cmd+Click on Mac

<a href="/page" @click.meta="openInNewTab">

Visual

Key Modifier Filtering

┌─────────────────────────────────────────────────────────────┐
│  @keyup.enter="submit"                                      │
│                                                             │
│  User presses a key:                                        │
│    │                                                        │
│    ▼                                                        │
│  event.key === 'Enter'?                                     │
│    │                                                        │
│    ├── YES ──▶ handler runs (submit)                        │
│    │                                                        │
│    └── NO ──▶ handler skipped                               │
│                                                             │
│  The filter is applied before the handler.                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

System + Key Combination

┌─────────────────────────────────────────────────────────────┐
│  @keyup.ctrl.enter="submit"                                 │
│                                                             │
│  User presses a key:                                        │
│    │                                                        │
│    ▼                                                        │
│  1. Check event.ctrlKey                                     │
│    ├── false ──▶ handler skipped                            │
│    └── true ──▶ continue                                    │
│    │                                                        │
│    ▼                                                        │
│  2. Check event.key === 'Enter'                             │
│    ├── false ──▶ handler skipped                            │
│    └── true ──▶ handler runs                                │
│                                                             │
│  Both conditions must match.                                │
│                                                             │
└─────────────────────────────────────────────────────────────┘

.exact Behavior

┌─────────────────────────────────────────────────────────────┐
│  @click.ctrl="handler"                                      │
│                                                             │
│  Ctrl + click          → fires                              │
│  Ctrl + Shift + click  → fires                              │
│  Ctrl + Alt + click    → fires                              │
│  Ctrl + Cmd + click    → fires                              │
│                                                             │
│  Fires whenever Ctrl is held.                               │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  @click.ctrl.exact="handler"                                │
│                                                             │
│  Ctrl + click          → fires                              │
│  Ctrl + Shift + click  → does NOT fire                      │
│  Ctrl + Alt + click    → does NOT fire                      │
│  Ctrl + Cmd + click    → does NOT fire                      │
│                                                             │
│  Fires only when Ctrl is the sole system modifier.          │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  @click.exact="handler"                                     │
│                                                             │
│  Plain click           → fires                              │
│  Ctrl + click          → does NOT fire                      │
│  Shift + click         → does NOT fire                      │
│                                                             │
│  Fires only when no system modifiers are held.              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Mouse Button Filtering

┌─────────────────────────────────────────────────────────────┐
│  @click.left="onLeft"                                       │
│                                                             │
│  event.button === 0?  (left button)                         │
│    ├── YES ──▶ handler runs                                 │
│    └── NO ──▶ handler skipped                               │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  @click.right.prevent="onRight"                             │
│                                                             │
│  1. event.button === 2?  (right button)                     │
│    ├── NO ──▶ handler skipped                               │
│    └── YES ──▶ continue                                     │
│  2. .prevent: event.preventDefault()                        │
│  3. handler runs (onRight)                                  │
│                                                             │
│  The native context menu is suppressed.                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Summary

ItemValue
Key modifiersFilter by event.key
.enter'Enter'
.tab'Tab'
.delete'Delete' or 'Backspace'
.esc'Escape'
.space' '
Arrow keys.up, .down, .left, .right
Custom keysKebab-case of event.key
Mouse modifiers.left, .middle, .right
System modifiers.ctrl, .alt, .shift, .meta
.exactExcludes unspecified modifiers
Modifier orderPhase, propagation, button, key, system, exact

Key takeaways:

  • Key modifiers filter keyboard events by event.key. The built-in aliases map to the standardized event.key values, not the deprecated keyCode. Use the kebab-case version of event.key for keys without a built-in alias .
  • The .delete alias matches both Delete and Backspace. This is intentional: the user’s intent is “delete,” and both keys serve that purpose on different keyboards.
  • Custom key modifiers use kebab-case. @keyup.page-down matches event.key === 'PageDown'. @keyup.f1 matches 'F1'. The rule is to convert the event.key value to kebab-case.
  • Mouse button modifiers filter by event.button. .left matches 0, .middle matches 1, .right matches 2. The .right modifier is commonly paired with .prevent to suppress the native context menu.
  • System modifiers filter by the state of Ctrl, Alt, Shift, and Meta. .ctrl fires whenever Ctrl is held, regardless of other modifiers. .exact tightens the match to exclude unspecified modifiers.
  • Modifiers combine. @keyup.ctrl.enter checks both event.ctrlKey and event.key === 'Enter'. @click.ctrl.left.exact checks Ctrl, the left button, and no other system modifiers. All conditions must match.
  • Order matters for readability, not usually for behavior. The conventional order is phase modifiers, propagation modifiers, button modifiers, key modifiers, system modifiers, then .exact. Following the convention makes the chain easier to read.

Remember: Key and mouse modifiers are filters. They run before your handler and decide whether it should run at all. @keyup.enter is not a shorthand for “call the handler on keyup”—it is a shorthand for “call the handler on keyup if the key was Enter.” The distinction matters because the handler does not need to check the key itself. The template expresses which events it cares about, and the framework handles the filtering. Use the built-in aliases for common keys, kebab-case for the rest, and .exact when the match must be precise. The result is handlers that receive exactly the events they were written for, and templates that document which interactions they respond to.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!