Docs
CodeRabbit
Cloudflare
AG Grid
SerpAPI
Netlify
OpenRouter
Neon
WorkOS
Clerk
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
CodeRabbit
Cloudflare
AG Grid
SerpAPI
Netlify
OpenRouter
Neon
WorkOS
Clerk
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
Hotkeys API Reference
App Hotkeys API Reference
Key hold & held keys API Reference
Hotkey Recorder API Reference
Normalization & format API Reference

Ember Example: UseHeldKeys

import { hash } from '@ember/helper'
import Component from '@glimmer/component'
import { tracked } from '@glimmer/tracking'

import { modifier } from 'ember-modifier'
import { cancel, schedule } from '@ember/runloop'
import { on } from '@ember/modifier'
import {
  formatForDisplay,
  useHeldKeys,
  useHeldKeyCodes,
} from '@tanstack/ember-hotkeys'

const neq = (a: unknown, b: unknown) => a !== b
const gt = (a: number, b: number) => a > b
const and = (a: unknown, b: unknown) => a && b

class App extends Component {
  usage0 =
    "import { useHeldKeys, useHeldKeyCodes } from '@tanstack/ember-hotkeys'\n\n// The containing component owns both subscriptions.\nheldKeys = useHeldKeys(this)\nheldCodes = useHeldKeyCodes(this)\n\n<template>\n  {{#each this.heldKeys.value as |key|}}\n    <kbd>{{key}}</kbd>\n  {{/each}}\n</template>"
  heldKeys = useHeldKeys(this)
  heldCodes = useHeldKeyCodes(this)
  @tracked history: Array<string> = []
  keyCode = (key: string) => this.heldCodes.value[key]
  clearHistory = () => (this.history = [])
  // Keep the optional history display in sync with the hook result.
  recordHistory = modifier((_element, [keys]: [Array<string>]) => {
    if (keys.length === 0) return
    const combo = keys
      .map((key) => formatForDisplay(key, { useSymbols: true }))
      .join(' + ')
    const timer = schedule('afterRender', () => {
      if (this.history.at(-1) !== combo) {
        this.history = [...this.history.slice(-9), combo]
      }
    })
    return () => cancel(timer)
  })
  <template>
    <div class='app'>
      <header>
        <h1>useHeldKeys</h1>
        <p>
          Returns an array of all currently pressed keys. Useful for displaying key
          combinations or building custom shortcut recording.
        </p>
      </header>

      <main>
        <section class='demo-section'>
          <h2>Currently Held Keys</h2>
          <div class='key-display'>
            {{#if (gt this.heldKeys.value.length 0)}}{{#each
                this.heldKeys.value
                as |key index|
              }}{{#let (this.keyCode key) as |code|}}
                  {{#if (gt index 0)}}<span class='plus'>+</span>{{/if}}
                  <kbd class='large'>
                    {{formatForDisplay key (hash useSymbols=true)}}
                    {{#if (and code (neq code key))}}<small
                        class='code-label'
                      >{{code}}</small>{{/if}}
                  </kbd>
                {{/let}}{{/each}}{{else}}<span class='placeholder'>Press any keys...</span>{{/if}}
          </div>
          <div class='stats'>
            Keys held:
            <strong>{{this.heldKeys.value.length}}</strong>
          </div>
        </section>

        <section class='demo-section'>
          <h2>Usage</h2>
          <pre class='code-block'>{{this.usage0}}</pre>
        </section>

        <section class='demo-section'>
          <h2>Try These Combinations</h2>
          <ul>
            <li>
              Hold
              <kbd>Shift</kbd>
              +
              <kbd>Control</kbd>
              +
              <kbd>A</kbd>
            </li>
            <li>Press multiple letter keys at once</li>
            <li>Hold modifiers and watch them appear</li>
            <li>Release keys one by one</li>
          </ul>
        </section>

        <section class='demo-section' {{this.recordHistory this.heldKeys.value}}>
          <h2>Recent Combinations</h2>
          {{#if (gt this.history.length 0)}}<ul class='history-list'>
              {{#each this.history as |combo i|}}<li>{{combo}}</li>{{/each}}
            </ul>{{else}}<p class='placeholder'>Press some key combinations...</p>{{/if}}
          <button {{on 'click' this.clearHistory}}>Clear History</button>
        </section>

        <section class='demo-section'>
          <h2>Use Cases</h2>
          <ul>
            <li>Building a keyboard shortcut recorder</li>
            <li>Displaying currently held keys to users</li>
            <li>Debugging keyboard input</li>
            <li>Creating key combination tutorials</li>
          </ul>
        </section>
      </main>

    </div>
  </template>
}
export default App
import { hash } from '@ember/helper'
import Component from '@glimmer/component'
import { tracked } from '@glimmer/tracking'

import { modifier } from 'ember-modifier'
import { cancel, schedule } from '@ember/runloop'
import { on } from '@ember/modifier'
import {
  formatForDisplay,
  useHeldKeys,
  useHeldKeyCodes,
} from '@tanstack/ember-hotkeys'

const neq = (a: unknown, b: unknown) => a !== b
const gt = (a: number, b: number) => a > b
const and = (a: unknown, b: unknown) => a && b

class App extends Component {
  usage0 =
    "import { useHeldKeys, useHeldKeyCodes } from '@tanstack/ember-hotkeys'\n\n// The containing component owns both subscriptions.\nheldKeys = useHeldKeys(this)\nheldCodes = useHeldKeyCodes(this)\n\n<template>\n  {{#each this.heldKeys.value as |key|}}\n    <kbd>{{key}}</kbd>\n  {{/each}}\n</template>"
  heldKeys = useHeldKeys(this)
  heldCodes = useHeldKeyCodes(this)
  @tracked history: Array<string> = []
  keyCode = (key: string) => this.heldCodes.value[key]
  clearHistory = () => (this.history = [])
  // Keep the optional history display in sync with the hook result.
  recordHistory = modifier((_element, [keys]: [Array<string>]) => {
    if (keys.length === 0) return
    const combo = keys
      .map((key) => formatForDisplay(key, { useSymbols: true }))
      .join(' + ')
    const timer = schedule('afterRender', () => {
      if (this.history.at(-1) !== combo) {
        this.history = [...this.history.slice(-9), combo]
      }
    })
    return () => cancel(timer)
  })
  <template>
    <div class='app'>
      <header>
        <h1>useHeldKeys</h1>
        <p>
          Returns an array of all currently pressed keys. Useful for displaying key
          combinations or building custom shortcut recording.
        </p>
      </header>

      <main>
        <section class='demo-section'>
          <h2>Currently Held Keys</h2>
          <div class='key-display'>
            {{#if (gt this.heldKeys.value.length 0)}}{{#each
                this.heldKeys.value
                as |key index|
              }}{{#let (this.keyCode key) as |code|}}
                  {{#if (gt index 0)}}<span class='plus'>+</span>{{/if}}
                  <kbd class='large'>
                    {{formatForDisplay key (hash useSymbols=true)}}
                    {{#if (and code (neq code key))}}<small
                        class='code-label'
                      >{{code}}</small>{{/if}}
                  </kbd>
                {{/let}}{{/each}}{{else}}<span class='placeholder'>Press any keys...</span>{{/if}}
          </div>
          <div class='stats'>
            Keys held:
            <strong>{{this.heldKeys.value.length}}</strong>
          </div>
        </section>

        <section class='demo-section'>
          <h2>Usage</h2>
          <pre class='code-block'>{{this.usage0}}</pre>
        </section>

        <section class='demo-section'>
          <h2>Try These Combinations</h2>
          <ul>
            <li>
              Hold
              <kbd>Shift</kbd>
              +
              <kbd>Control</kbd>
              +
              <kbd>A</kbd>
            </li>
            <li>Press multiple letter keys at once</li>
            <li>Hold modifiers and watch them appear</li>
            <li>Release keys one by one</li>
          </ul>
        </section>

        <section class='demo-section' {{this.recordHistory this.heldKeys.value}}>
          <h2>Recent Combinations</h2>
          {{#if (gt this.history.length 0)}}<ul class='history-list'>
              {{#each this.history as |combo i|}}<li>{{combo}}</li>{{/each}}
            </ul>{{else}}<p class='placeholder'>Press some key combinations...</p>{{/if}}
          <button {{on 'click' this.clearHistory}}>Clear History</button>
        </section>

        <section class='demo-section'>
          <h2>Use Cases</h2>
          <ul>
            <li>Building a keyboard shortcut recorder</li>
            <li>Displaying currently held keys to users</li>
            <li>Debugging keyboard input</li>
            <li>Creating key combination tutorials</li>
          </ul>
        </section>
      </main>

    </div>
  </template>
}
export default App