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 injectHotkey API is the primary way to register keyboard shortcuts in Angular applications. It wraps the singleton HotkeyManager with injection-context lifecycle management and Angular signal-friendly reactive options.

Basic Usage

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

@Component({ standalone: true, template: `` })
export class AppComponent {
  constructor() {
    injectHotkey('Mod+S', () => {
      saveDocument()
    })
  }
}
import { Component } from '@angular/core'
import { injectHotkey } from '@tanstack/angular-hotkeys'

@Component({ standalone: true, template: `` })
export class AppComponent {
  constructor() {
    injectHotkey('Mod+S', () => {
      saveDocument()
    })
  }
}

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

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

Default Options

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

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

Reactive Options

For reactive state, pass an accessor function as the third argument.

enabled

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

ts
import { Component, signal } from '@angular/core'
import { injectHotkey } from '@tanstack/angular-hotkeys'

@Component({ standalone: true, template: `` })
export class EditorComponent {
  readonly isEditing = signal(false)

  constructor() {
    injectHotkey('Mod+S', () => save(), () => ({
      enabled: this.isEditing(),
    }))
  }
}
import { Component, signal } from '@angular/core'
import { injectHotkey } from '@tanstack/angular-hotkeys'

@Component({ standalone: true, template: `` })
export class EditorComponent {
  readonly isEditing = signal(false)

  constructor() {
    injectHotkey('Mod+S', () => save(), () => ({
      enabled: this.isEditing(),
    }))
  }
}

target

ts
import { Component, ElementRef, viewChild } from '@angular/core'
import { injectHotkey } from '@tanstack/angular-hotkeys'

@Component({
  standalone: true,
  template: `<div #panel tabindex="0">Panel content</div>`,
})
export class PanelComponent {
  private readonly panel = viewChild<ElementRef<HTMLDivElement>>('panel')

  constructor() {
    injectHotkey('Escape', () => closePanel(), () => ({
      target: this.panel()?.nativeElement ?? null,
    }))
  }
}
import { Component, ElementRef, viewChild } from '@angular/core'
import { injectHotkey } from '@tanstack/angular-hotkeys'

@Component({
  standalone: true,
  template: `<div #panel tabindex="0">Panel content</div>`,
})
export class PanelComponent {
  private readonly panel = viewChild<ElementRef<HTMLDivElement>>('panel')

  constructor() {
    injectHotkey('Escape', () => closePanel(), () => ({
      target: this.panel()?.nativeElement ?? null,
    }))
  }
}

Global Default Options via Provider

ts
import { ApplicationConfig } from '@angular/core'
import { provideHotkeys } from '@tanstack/angular-hotkeys'

export const appConfig: ApplicationConfig = {
  providers: [
    provideHotkeys({
      hotkey: { preventDefault: false, ignoreInputs: false },
    }),
  ],
}
import { ApplicationConfig } from '@angular/core'
import { provideHotkeys } from '@tanstack/angular-hotkeys'

export const appConfig: ApplicationConfig = {
  providers: [
    provideHotkeys({
      hotkey: { preventDefault: false, ignoreInputs: false },
    }),
  ],
}

Common Options

requireReset

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

ignoreInputs

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

conflictBehavior

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

platform

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

Automatic Cleanup

Registrations are cleaned up automatically when the owning injection context is destroyed.

Registering Multiple Hotkeys

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

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

@Component({ standalone: true, template: `` })
export class EditorComponent {
  constructor() {
    injectHotkeys([
      { hotkey: 'Mod+S', callback: () => this.save() },
      { hotkey: 'Mod+Z', callback: () => this.undo() },
      { hotkey: 'Escape', callback: () => this.close() },
    ])
  }
}
import { Component } from '@angular/core'
import { injectHotkeys } from '@tanstack/angular-hotkeys'

@Component({ standalone: true, template: `` })
export class EditorComponent {
  constructor() {
    injectHotkeys([
      { hotkey: 'Mod+S', callback: () => this.save() },
      { hotkey: 'Mod+Z', callback: () => this.undo() },
      { hotkey: 'Escape', callback: () => this.close() },
    ])
  }
}

Common Options with Per-Hotkey Overrides

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

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

Dynamic Hotkey Lists

Pass a getter for reactive arrays driven by Angular signals:

ts
shortcuts = signal([...])

constructor() {
  injectHotkeys(
    () => this.shortcuts().map((s) => ({
      hotkey: s.key,
      callback: s.action,
    })),
  )
}
shortcuts = signal([...])

constructor() {
  injectHotkeys(
    () => this.shortcuts().map((s) => ({
      hotkey: s.key,
      callback: s.action,
    })),
  )
}

The function tracks signal dependencies 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
injectHotkey('Mod+S', () => save(), {
  meta: { name: 'Save', description: 'Save the document' },
})
injectHotkey('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
  }
}

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

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

Introspecting Registrations

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

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

@Component({
  standalone: true,
  template: `
    <h2>Keyboard Shortcuts</h2>
    <ul>
      @for (reg of registrations().hotkeys; track reg.hotkey) {
        <li>
          <kbd>{{ reg.hotkey }}</kbd>
          @if (reg.meta?.name) {
            <span> — {{ reg.meta.name }}</span>
          }
          @if (reg.meta?.description) {
            <p>{{ reg.meta.description }}</p>
          }
        </li>
      }
    </ul>
    @if (registrations().sequences.length > 0) {
      <h2>Sequences</h2>
      <ul>
        @for (reg of registrations().sequences; track reg.sequence.join(' ')) {
          <li>
            <kbd>{{ reg.sequence.join(' → ') }}</kbd>
            @if (reg.meta?.name) {
              <span> — {{ reg.meta.name }}</span>
            }
          </li>
        }
      </ul>
    }
  `,
})
export class ShortcutPaletteComponent {
  readonly registrations = injectHotkeyRegistrations()
}
import { Component } from '@angular/core'
import { injectHotkeyRegistrations } from '@tanstack/angular-hotkeys'

@Component({
  standalone: true,
  template: `
    <h2>Keyboard Shortcuts</h2>
    <ul>
      @for (reg of registrations().hotkeys; track reg.hotkey) {
        <li>
          <kbd>{{ reg.hotkey }}</kbd>
          @if (reg.meta?.name) {
            <span> — {{ reg.meta.name }}</span>
          }
          @if (reg.meta?.description) {
            <p>{{ reg.meta.description }}</p>
          }
        </li>
      }
    </ul>
    @if (registrations().sequences.length > 0) {
      <h2>Sequences</h2>
      <ul>
        @for (reg of registrations().sequences; track reg.sequence.join(' ')) {
          <li>
            <kbd>{{ reg.sequence.join(' → ') }}</kbd>
            @if (reg.meta?.name) {
              <span> — {{ reg.meta.name }}</span>
            }
          </li>
        }
      </ul>
    }
  `,
})
export class ShortcutPaletteComponent {
  readonly registrations = injectHotkeyRegistrations()
}

The returned signal provides an object with a hotkeys array containing registration objects with the hotkey string, options (including meta), and enabled state, and a sequences array containing sequence registrations with the same structure.

The Hotkey Manager

You can access the underlying manager directly when needed:

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

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

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