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.
<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:
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)
})useHotkey uses the same core defaults as the framework-agnostic manager:
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',
})Vue-specific options can be plain values, refs, or getters.
When enabled is false, the hotkey stays registered (visible in devtools); only the callback is suppressed.
<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><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><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>useHotkey('Escape', () => closePanel(), { requireReset: true })useHotkey('Escape', () => closePanel(), { requireReset: true })useHotkey('K', () => openSearch())
useHotkey('Enter', () => submit(), { ignoreInputs: false })useHotkey('K', () => openSearch())
useHotkey('Enter', () => submit(), { ignoreInputs: false })useHotkey('Mod+S', () => save(), { conflictBehavior: 'replace' })useHotkey('Mod+S', () => save(), { conflictBehavior: 'replace' })useHotkey('Mod+S', () => save(), { platform: 'mac' })useHotkey('Mod+S', () => save(), { platform: 'mac' })Hotkeys are automatically unregistered when the owning component unmounts.
When you need to register several hotkeys at once — or a dynamic, variable-length list — use the useHotkeys (plural) composable:
<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>Pass shared options as the second argument. Per-definition options override the common ones:
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 },
)Pass a getter or computed ref as the first argument for reactive arrays:
<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.
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.
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:
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' },
})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.
<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.
You can always reach for the underlying manager directly:
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()