Docs
CodeRabbit
Cloudflare
Railway
Netlify
OpenRouter
Clerk
AG Grid
WorkOS
SerpAPI
Electric
Prisma
Sentry
Unkey
CodeRabbit
Cloudflare
Railway
Netlify
OpenRouter
Clerk
AG Grid
WorkOS
SerpAPI
Electric
Prisma
Sentry
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 Angular APIs for tracking live keyboard state: , , and .

ts
import { Component } from '@angular/core'
import { injectHeldKeys } from '@tanstack/angular-hotkeys'

@Component({
  standalone: true,
  template: `
    <div>
      {{ heldKeys().length > 0 ? heldKeys().join(' + ') : 'No keys held' }}
    </div>
  `,
})
export class KeyDisplayComponent {
  readonly heldKeys = injectHeldKeys()
}
import { Component } from '@angular/core'
import { injectHeldKeys } from '@tanstack/angular-hotkeys'

@Component({
  standalone: true,
  template: `
    <div>
      {{ heldKeys().length > 0 ? heldKeys().join(' + ') : 'No keys held' }}
    </div>
  `,
})
export class KeyDisplayComponent {
  readonly heldKeys = injectHeldKeys()
}

ts
import { injectHeldKeyCodes } from '@tanstack/angular-hotkeys'

readonly heldCodes = injectHeldKeyCodes()
import { injectHeldKeyCodes } from '@tanstack/angular-hotkeys'

readonly heldCodes = injectHeldKeyCodes()

ts
import { injectKeyHold } from '@tanstack/angular-hotkeys'

readonly isShiftHeld = injectKeyHold('Shift')
import { injectKeyHold } from '@tanstack/angular-hotkeys'

readonly isShiftHeld = injectKeyHold('Shift')

Common Patterns

Hold-to-Reveal UI

ts
import { Component } from '@angular/core'
import { injectKeyHold } from '@tanstack/angular-hotkeys'

@Component({
  standalone: true,
  template: `
    @if (isShiftHeld()) {
      <button>Permanently Delete</button>
    } @else {
      <button>Move to Trash</button>
    }
  `,
})
export class FileActionsComponent {
  readonly isShiftHeld = injectKeyHold('Shift')
}
import { Component } from '@angular/core'
import { injectKeyHold } from '@tanstack/angular-hotkeys'

@Component({
  standalone: true,
  template: `
    @if (isShiftHeld()) {
      <button>Permanently Delete</button>
    } @else {
      <button>Move to Trash</button>
    }
  `,
})
export class FileActionsComponent {
  readonly isShiftHeld = injectKeyHold('Shift')
}

Debugging Key Display

ts
import { Component } from '@angular/core'
import {
  formatForDisplay,
  injectHeldKeyCodes,
  injectHeldKeys,
  type RegisterableHotkey,
} from '@tanstack/angular-hotkeys'

@Component({ standalone: true, template: `` })
export class KeyDebuggerComponent {
  readonly heldKeys = injectHeldKeys()
  readonly heldCodes = injectHeldKeyCodes()
  readonly formatKey = (k: string) =>
    formatForDisplay(k as RegisterableHotkey, { useSymbols: true })
}
import { Component } from '@angular/core'
import {
  formatForDisplay,
  injectHeldKeyCodes,
  injectHeldKeys,
  type RegisterableHotkey,
} from '@tanstack/angular-hotkeys'

@Component({ standalone: true, template: `` })
export class KeyDebuggerComponent {
  readonly heldKeys = injectHeldKeys()
  readonly heldCodes = injectHeldKeyCodes()
  readonly formatKey = (k: string) =>
    formatForDisplay(k as RegisterableHotkey, { useSymbols: true })
}

Under the Hood

All three APIs subscribe to the singleton :

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

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

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