useHotkey
A composable for handling hotkey combinations and sequences with platform-aware modifiers and automatic cleanup.
Usage
The useHotkey composable registers hotkey handlers on the window with automatic cleanup when the component is unmounted. It supports key combinations (ctrl+k), key sequences (g-h), and platform-aware modifier mapping.
UseHotkey
<script setup lang="ts">
import { useHotkey } from '@vuetify/v0'
import { ref } from 'vue'
const commandPaletteOpen = ref(false)
const message = ref('')
// Simple key combination - Ctrl+K (Cmd+K on Mac)
useHotkey('ctrl+k', () => {
commandPaletteOpen.value = true
})
// Key sequence (GitHub-style) - press 'g' then 'h'
useHotkey('g-h', () => {
message.value = 'Go home!'
})
// With multiple modifiers
useHotkey('ctrl+shift+s', () => {
message.value = 'Save all!'
})
// Allow in inputs with the inputs option
useHotkey('escape', () => {
commandPaletteOpen.value = false
}, { inputs: true })
</script>
<template>
<div>
<p>Press Ctrl+K to open command palette</p>
<p>Press g then h for GitHub-style navigation</p>
<p v-if="message">{{ message }}</p>
</div>
</template>Examples
<script setup lang="ts">
import { Dialog, useHotkey, useToggleScope } from '@vuetify/v0'
import { IN_BROWSER } from '@vuetify/v0/constants'
import { computed, shallowRef, useTemplateRef, watch } from 'vue'
const isOpen = shallowRef(false)
const query = shallowRef('')
const selectedIndex = shallowRef(0)
const inputRef = useTemplateRef('input')
const isMac = IN_BROWSER && navigator.userAgent.includes('Mac')
const modKey = isMac ? '⌘' : 'Ctrl'
const lastAction = shallowRef('')
const commands = [
{ id: 'theme', label: 'Toggle theme', icon: 'i-mdi-brightness-6', hotkey: 'L', action: () => lastAction.value = 'Toggled theme' },
{ id: 'search', label: 'Search docs', icon: 'i-mdi-magnify', hotkey: 'S', action: () => lastAction.value = 'Opened search' },
{ id: 'home', label: 'Go to home', icon: 'i-mdi-home', hotkey: 'H', action: () => lastAction.value = 'Navigated home' },
{ id: 'github', label: 'View on GitHub', icon: 'i-mdi-github', hotkey: 'G', action: () => lastAction.value = 'Opened GitHub' },
{ id: 'copy', label: 'Copy link', icon: 'i-mdi-link', hotkey: 'C', action: () => lastAction.value = 'Copied link' },
{ id: 'feedback', label: 'Send feedback', icon: 'i-mdi-message-outline', hotkey: 'F', action: () => lastAction.value = 'Opened feedback' },
]
const filtered = computed(() =>
commands.filter(c => c.label.toLowerCase().includes(query.value.toLowerCase())),
)
// Global hotkey to open palette
useHotkey('cmd+j', () => {
isOpen.value = true
})
// Hotkeys active only when palette is open
useToggleScope(isOpen, () => {
// Command shortcuts (inputs: true to work while typing in search)
for (const cmd of commands) {
useHotkey(`cmd+${cmd.hotkey.toLowerCase()}`, () => {
cmd.action()
isOpen.value = false
}, { inputs: true })
}
// Navigation
useHotkey('arrowdown', () => {
selectedIndex.value = (selectedIndex.value + 1) % filtered.value.length
}, { inputs: true })
useHotkey('arrowup', () => {
selectedIndex.value = (selectedIndex.value - 1 + filtered.value.length) % filtered.value.length
}, { inputs: true })
})
watch(isOpen, open => {
if (open) {
query.value = ''
selectedIndex.value = 0
setTimeout(() => inputRef.value?.focus(), 0)
}
})
watch(filtered, () => {
selectedIndex.value = 0
})
</script>
<template>
<div class="text-center">
<Dialog.Root v-model="isOpen">
<Dialog.Activator class="inline-flex items-center gap-3 px-4 py-2 bg-surface border border-divider rounded-lg hover:border-primary transition-colors">
<span class="i-mdi-magnify text-on-surface-variant" />
<span class="text-on-surface-variant">Search commands...</span>
<kbd class="ml-4 px-1.5 py-0.5 text-xs font-mono bg-surface-variant text-on-surface-variant rounded">
{{ modKey }}+J
</kbd>
</Dialog.Activator>
<p v-if="lastAction" class="mt-3 text-sm text-on-surface-variant">
Last action: <span class="text-primary font-medium">{{ lastAction }}</span>
</p>
<Dialog.Content class="w-full max-w-lg mx-auto mt-[15vh] rounded-xl bg-surface border border-divider shadow-2xl overflow-hidden text-left">
<div class="flex items-center gap-3 px-4 py-3 border-b border-divider">
<span class="i-mdi-magnify text-xl text-on-surface-variant" />
<input
ref="input"
v-model="query"
class="flex-1 bg-transparent text-on-surface outline-none placeholder:text-on-surface-variant"
placeholder="Type a command..."
>
<kbd class="px-1.5 py-0.5 text-xs font-mono bg-surface-variant text-on-surface-variant rounded">ESC</kbd>
</div>
<div class="max-h-72 overflow-auto py-2">
<Dialog.Close
v-for="(cmd, i) in filtered"
:key="cmd.id"
as="button"
class="w-full flex items-center gap-3 px-4 py-2.5 text-left transition-colors"
:class="i === selectedIndex ? 'bg-primary/10 text-primary' : 'text-on-surface hover:bg-surface-variant'"
@click="cmd.action()"
@mouseenter="selectedIndex = i"
>
<span class="text-lg" :class="cmd.icon" />
<span class="flex-1">{{ cmd.label }}</span>
<kbd class="px-1.5 py-0.5 text-xs font-mono bg-surface-variant/50 rounded">
{{ modKey }}+{{ cmd.hotkey }}
</kbd>
</Dialog.Close>
<div v-if="filtered.length === 0" class="px-4 py-8 text-center text-on-surface-variant">
No commands found
</div>
</div>
<div class="flex items-center gap-4 px-4 py-2 border-t border-divider text-xs text-on-surface-variant">
<span class="flex items-center gap-1"><kbd class="px-1 py-0.5 bg-surface-variant rounded">↑</kbd><kbd class="px-1 py-0.5 bg-surface-variant rounded">↓</kbd> navigate</span>
<span class="flex items-center gap-1"><kbd class="px-1 py-0.5 bg-surface-variant rounded">↵</kbd> select</span>
<span class="flex items-center gap-1"><kbd class="px-1 py-0.5 bg-surface-variant rounded">esc</kbd> close</span>
</div>
</Dialog.Content>
</Dialog.Root>
</div>
</template>
Architecture
useHotkey builds on useEventListener for keyboard event handling:
Reactivity
| Property/Method | Reactive | Notes |
|---|---|---|
isActive | Computed from cleanup ref | |
isPaused | ShallowRef, readonly | |
keys | Accepts MaybeRefOrGetter, watched for changes |
The following API details are for the useHotkey composable.
Functions
useHotkey
(keys: MaybeRefOrGetter<string>, callback: (e: KeyboardEvent) => void, options?: UseHotkeyOptions, _platform?: PlatformContext) => UseHotkeyReturnA composable that listens for hotkey combinations and sequences.
Options
inputs
MaybeRefOrGetter<boolean>Whether to trigger the callback when an input element is focused.
Default: false
Properties
isActive
Readonly<Ref<boolean, boolean>>Whether the hotkey listener is currently active (listening for keys). False when paused, when keys is undefined, or in SSR.
Methods
Was this page helpful?