Docs
CodeRabbit
Cloudflare
Railway
WorkOS
Clerk
SerpAPI
Netlify
OpenRouter
AG Grid
Sentry
Prisma
Electric
Unkey
CodeRabbit
Cloudflare
Railway
WorkOS
Clerk
SerpAPI
Netlify
OpenRouter
AG Grid
Sentry
Prisma
Electric
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

Key State Tracking Guide

TanStack Hotkeys provides three Vue composables for tracking live keyboard state: , , and .

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

const heldKeys = useHeldKeys()
</script>

<template>
  <div>{{ heldKeys.length > 0 ? heldKeys.join(' + ') : 'No keys held' }}</div>
</template>
<script setup lang="ts">
import { useHeldKeys } from '@tanstack/vue-hotkeys'

const heldKeys = useHeldKeys()
</script>

<template>
  <div>{{ heldKeys.length > 0 ? heldKeys.join(' + ') : 'No keys held' }}</div>
</template>

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

const heldCodes = useHeldKeyCodes()
</script>
<script setup lang="ts">
import { useHeldKeyCodes } from '@tanstack/vue-hotkeys'

const heldCodes = useHeldKeyCodes()
</script>

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

const isShiftHeld = useKeyHold('Shift')
</script>

<template>
  <span :class="{ active: isShiftHeld }">Shift</span>
</template>
<script setup lang="ts">
import { useKeyHold } from '@tanstack/vue-hotkeys'

const isShiftHeld = useKeyHold('Shift')
</script>

<template>
  <span :class="{ active: isShiftHeld }">Shift</span>
</template>

Common Patterns

Hold-to-Reveal UI

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

const isShiftHeld = useKeyHold('Shift')
</script>

<template>
  <button v-if="isShiftHeld">Permanently Delete</button>
  <button v-else>Move to Trash</button>
</template>
<script setup lang="ts">
import { useKeyHold } from '@tanstack/vue-hotkeys'

const isShiftHeld = useKeyHold('Shift')
</script>

<template>
  <button v-if="isShiftHeld">Permanently Delete</button>
  <button v-else>Move to Trash</button>
</template>

Debugging Key Display

vue
<script setup lang="ts">
import {
  formatForDisplay,
  useHeldKeyCodes,
  useHeldKeys,
} from '@tanstack/vue-hotkeys'
import type { RegisterableHotkey } from '@tanstack/vue-hotkeys'

const heldKeys = useHeldKeys()
const heldCodes = useHeldKeyCodes()
</script>

<template>
  <div v-for="key in heldKeys" :key="key">
    <strong>{{
      formatForDisplay(key as RegisterableHotkey, { useSymbols: true })
    }}</strong>
    <span>{{ heldCodes[key] }}</span>
  </div>
</template>
<script setup lang="ts">
import {
  formatForDisplay,
  useHeldKeyCodes,
  useHeldKeys,
} from '@tanstack/vue-hotkeys'
import type { RegisterableHotkey } from '@tanstack/vue-hotkeys'

const heldKeys = useHeldKeys()
const heldCodes = useHeldKeyCodes()
</script>

<template>
  <div v-for="key in heldKeys" :key="key">
    <strong>{{
      formatForDisplay(key as RegisterableHotkey, { useSymbols: true })
    }}</strong>
    <span>{{ heldCodes[key] }}</span>
  </div>
</template>

Under the Hood

All three composables subscribe to the singleton :

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

const tracker = getKeyStateTracker()
tracker.getHeldKeys()
tracker.isKeyHeld('Shift')
import { getKeyStateTracker } from '@tanstack/vue-hotkeys'

const tracker = getKeyStateTracker()
tracker.getHeldKeys()
tracker.isKeyHeld('Shift')