Docs
CodeRabbit
Cloudflare
AG Grid
SerpAPI
Netlify
OpenRouter
WorkOS
Clerk
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
CodeRabbit
Cloudflare
AG Grid
SerpAPI
Netlify
OpenRouter
WorkOS
Clerk
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
Hotkeys API Reference
Hotkey Sequence API Reference
Key hold & held keys API Reference
Hotkey Recorder API Reference
Hotkey Sequence Recorder API Reference
Normalization & format API Reference
Guides

Hotkeys Guide

The useHotkey composable is the primary way to register keyboard shortcuts in Vue applications. It wraps the singleton HotkeyManager with automatic cleanup, support for template refs, and reactive option syncing.

Basic Usage

vue
<script setup lang="ts">
import { useHotkey } from '@tanstack/vue-hotkeys'

useHotkey('Mod+S', () => {
  saveDocument()
})
</script>
<script setup lang="ts">
import { useHotkey } from '@tanstack/vue-hotkeys'

useHotkey('Mod+S', () => {
  saveDocument()
})
</script>

The callback receives the original KeyboardEvent as the first argument and a HotkeyCallbackContext as the second:

ts
useHotkey('Mod+S', (event, context) => {
  console.log(context.hotkey)
  console.log(context.parsedHotkey)
})
useHotkey('Mod+S', (event, context) => {
  console.log(context.hotkey)
  console.log(context.parsedHotkey)
})

Default Options

useHotkey uses the same core defaults as the framework-agnostic manager:

ts
useHotkey('Mod+S', callback, {
  enabled: true,
  preventDefault: true,
  stopPropagation: true,
  eventType: 'keydown',
  requireReset: false,
  ignoreInputs: undefined,
  target: document,
  platform: undefined,
  conflictBehavior: 'warn',
})
useHotkey('Mod+S', callback, {
  enabled: true,
  preventDefault: true,
  stopPropagation: true,
  eventType: 'keydown',
  requireReset: false,
  ignoreInputs: undefined,
  target: document,
  platform: undefined,
  conflictBehavior: 'warn',
})

Reactive Options

Vue-specific options can be plain values, refs, or getters.

enabled

When enabled is false, the hotkey stays registered (visible in devtools); only the callback is suppressed.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { useHotkey } from '@tanstack/vue-hotkeys'

const isEditing = ref(false)

useHotkey('Mod+S', () => save(), { enabled: isEditing })
</script>
<script setup lang="ts">
import { ref } from 'vue'
import { useHotkey } from '@tanstack/vue-hotkeys'

const isEditing = ref(false)

useHotkey('Mod+S', () => save(), { enabled: isEditing })
</script>

target

vue
<script setup lang="ts">
import { ref } from 'vue'
import { useHotkey } from '@tanstack/vue-hotkeys'

const panelRef = ref<HTMLDivElement | null>(null)

useHotkey('Escape', () => closePanel(), { target: panelRef })
</script>

<template>
  <div ref="panelRef" tabindex="0">Panel content</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useHotkey } from '@tanstack/vue-hotkeys'

const panelRef = ref<HTMLDivElement | null>(null)

useHotkey('Escape', () => closePanel(), { target: panelRef })
</script>

<template>
  <div ref="panelRef" tabindex="0">Panel content</div>
</template>

Global Default Options via Provider

vue
<script setup lang="ts">
import { HotkeysProvider } from '@tanstack/vue-hotkeys'
</script>

<template>
  <HotkeysProvider
    :default-options="{
      hotkey: { preventDefault: false, ignoreInputs: false },
    }"
  >
    <AppContent />
  </HotkeysProvider>
</template>
<script setup lang="ts">
import { HotkeysProvider } from '@tanstack/vue-hotkeys'
</script>

<template>
  <HotkeysProvider
    :default-options="{
      hotkey: { preventDefault: false, ignoreInputs: false },
    }"
  >
    <AppContent />
  </HotkeysProvider>
</template>

Common Options

requireReset

ts
useHotkey('Escape', () => closePanel(), { requireReset: true })
useHotkey('Escape', () => closePanel(), { requireReset: true })

ignoreInputs

ts
useHotkey('K', () => openSearch())
useHotkey('Enter', () => submit(), { ignoreInputs: false })
useHotkey('K', () => openSearch())
useHotkey('Enter', () => submit(), { ignoreInputs: false })

conflictBehavior

ts
useHotkey('Mod+S', () => save(), { conflictBehavior: 'replace' })
useHotkey('Mod+S', () => save(), { conflictBehavior: 'replace' })

platform

ts
useHotkey('Mod+S', () => save(), { platform: 'mac' })
useHotkey('Mod+S', () => save(), { platform: 'mac' })

Automatic Cleanup

Hotkeys are automatically unregistered when the owning component unmounts.

Registering Multiple Hotkeys

When you need to register several hotkeys at once — or a dynamic, variable-length list — use the useHotkeys (plural) composable:

vue
<script setup>
import { useHotkeys } from '@tanstack/vue-hotkeys'

useHotkeys([
  { hotkey: 'Mod+S', callback: () => save() },
  { hotkey: 'Mod+Z', callback: () => undo() },
  { hotkey: 'Escape', callback: () => close() },
])
</script>
<script setup>
import { useHotkeys } from '@tanstack/vue-hotkeys'

useHotkeys([
  { hotkey: 'Mod+S', callback: () => save() },
  { hotkey: 'Mod+Z', callback: () => undo() },
  { hotkey: 'Escape', callback: () => close() },
])
</script>

Common Options with Per-Hotkey Overrides

Pass shared options as the second argument. Per-definition options override the common ones:

ts
useHotkeys(
  [
    { hotkey: 'Mod+S', callback: () => save() },
    { hotkey: 'Mod+Z', callback: () => undo(), options: { enabled: false } },
  ],
  { preventDefault: true },
)
useHotkeys(
  [
    { hotkey: 'Mod+S', callback: () => save() },
    { hotkey: 'Mod+Z', callback: () => undo(), options: { enabled: false } },
  ],
  { preventDefault: true },
)

Dynamic Hotkey Lists

Pass a getter or computed ref as the first argument for reactive arrays:

vue
<script setup>
import { computed } from 'vue'
import { useHotkeys } from '@tanstack/vue-hotkeys'

const items = computed(() => [...])

useHotkeys(
  () => items.value.map((item) => ({
    hotkey: item.shortcut,
    callback: item.action,
  })),
)
</script>
<script setup>
import { computed } from 'vue'
import { useHotkeys } from '@tanstack/vue-hotkeys'

const items = computed(() => [...])

useHotkeys(
  () => items.value.map((item) => ({
    hotkey: item.shortcut,
    callback: item.action,
  })),
)
</script>

The composable watches for changes and diffs registrations automatically.

Metadata (name & description)

Every hotkey registration can carry a meta object with a name and description. This metadata is informational only -- it does not affect hotkey behavior -- but it flows through to registrations and devtools, making it easy to build shortcut palettes and help screens.

ts
useHotkey('Mod+S', () => save(), {
  meta: { name: 'Save', description: 'Save the document' },
})
useHotkey('Mod+S', () => save(), {
  meta: { name: 'Save', description: 'Save the document' },
})

The meta option is typed as HotkeyMeta, which ships with name and description fields. You can extend it with additional properties using TypeScript declaration merging:

ts
declare module '@tanstack/hotkeys' {
  interface HotkeyMeta {
    icon?: string
    group?: string
  }
}

useHotkey('Mod+S', () => save(), {
  meta: { name: 'Save', description: 'Save the document', icon: 'floppy', group: 'File' },
})
declare module '@tanstack/hotkeys' {
  interface HotkeyMeta {
    icon?: string
    group?: string
  }
}

useHotkey('Mod+S', () => save(), {
  meta: { name: 'Save', description: 'Save the document', icon: 'floppy', group: 'File' },
})

Introspecting Registrations

Use the useHotkeyRegistrations composable to get a live view of all hotkey and sequence registrations. This is useful for building shortcut palettes, help dialogs, or devtools.

vue
<script setup lang="ts">
import { useHotkeyRegistrations } from '@tanstack/vue-hotkeys'

const { hotkeys, sequences } = useHotkeyRegistrations()
</script>

<template>
  <div>
    <h2>Keyboard Shortcuts</h2>
    <ul>
      <li v-for="reg in hotkeys" :key="reg.hotkey">
        <kbd>{{ reg.hotkey }}</kbd>
        <span v-if="reg.meta?.name"> — {{ reg.meta.name }}</span>
        <p v-if="reg.meta?.description">{{ reg.meta.description }}</p>
      </li>
    </ul>
    <template v-if="sequences.length > 0">
      <h2>Sequences</h2>
      <ul>
        <li v-for="reg in sequences" :key="reg.sequence.join(' ')">
          <kbd>{{ reg.sequence.join(' → ') }}</kbd>
          <span v-if="reg.meta?.name"> — {{ reg.meta.name }}</span>
        </li>
      </ul>
    </template>
  </div>
</template>
<script setup lang="ts">
import { useHotkeyRegistrations } from '@tanstack/vue-hotkeys'

const { hotkeys, sequences } = useHotkeyRegistrations()
</script>

<template>
  <div>
    <h2>Keyboard Shortcuts</h2>
    <ul>
      <li v-for="reg in hotkeys" :key="reg.hotkey">
        <kbd>{{ reg.hotkey }}</kbd>
        <span v-if="reg.meta?.name"> — {{ reg.meta.name }}</span>
        <p v-if="reg.meta?.description">{{ reg.meta.description }}</p>
      </li>
    </ul>
    <template v-if="sequences.length > 0">
      <h2>Sequences</h2>
      <ul>
        <li v-for="reg in sequences" :key="reg.sequence.join(' ')">
          <kbd>{{ reg.sequence.join(' → ') }}</kbd>
          <span v-if="reg.meta?.name"> — {{ reg.meta.name }}</span>
        </li>
      </ul>
    </template>
  </div>
</template>

The returned hotkeys array contains registration objects with the hotkey string, options (including meta), and enabled state. The sequences array contains sequence registrations with the same structure.

The Hotkey Manager

You can always reach for the underlying manager directly:

ts
import { getHotkeyManager } from '@tanstack/vue-hotkeys'

const manager = getHotkeyManager()
manager.isRegistered('Mod+S')
manager.getRegistrationCount()
import { getHotkeyManager } from '@tanstack/vue-hotkeys'

const manager = getHotkeyManager()
manager.isRegistered('Mod+S')
manager.getRegistrationCount()