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
API Reference
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

Svelte Example: Create Hotkeys

svelte
<script lang="ts">
  import {
    createHotkeys,
    formatForDisplay,
    getHotkeyRegistrations,
  } from '@tanstack/svelte-hotkeys'
  import type { Hotkey, CreateHotkeyDefinition } from '@tanstack/svelte-hotkeys'

  // Basic demo
  let log = $state<Array<string>>([])
  let saveCount = $state(0)
  let undoCount = $state(0)
  let redoCount = $state(0)

  createHotkeys([
    {
      hotkey: 'Shift+S',
      callback: (_e: KeyboardEvent, { hotkey }: { hotkey: string }) => {
        saveCount++
        log = [`${hotkey} pressed`, ...log].slice(0, 20)
      },
      options: {
        meta: { name: 'Save', description: 'Save the current document' },
      },
    },
    {
      hotkey: 'Shift+U',
      callback: (_e: KeyboardEvent, { hotkey }: { hotkey: string }) => {
        undoCount++
        log = [`${hotkey} pressed`, ...log].slice(0, 20)
      },
      options: {
        meta: { name: 'Undo', description: 'Undo the last action' },
      },
    },
    {
      hotkey: 'Shift+R',
      callback: (_e: KeyboardEvent, { hotkey }: { hotkey: string }) => {
        redoCount++
        log = [`${hotkey} pressed`, ...log].slice(0, 20)
      },
      options: {
        meta: { name: 'Redo', description: 'Redo the last undone action' },
      },
    },
  ])

  // Common options demo
  let commonEnabled = $state(true)
  let counts = $state({ a: 0, b: 0, c: 0 })

  createHotkeys(
    [
      {
        hotkey: 'Alt+J',
        callback: () => {
          counts = { ...counts, a: counts.a + 1 }
        },
        options: {
          meta: {
            name: 'Action A',
            description: 'First action (respects toggle)',
          },
        },
      },
      {
        hotkey: 'Alt+K',
        callback: () => {
          counts = { ...counts, b: counts.b + 1 }
        },
        options: {
          meta: {
            name: 'Action B',
            description: 'Second action (respects toggle)',
          },
        },
      },
      {
        hotkey: 'Alt+L',
        callback: () => {
          counts = { ...counts, c: counts.c + 1 }
        },
        options: {
          enabled: true,
          meta: {
            name: 'Action C',
            description: 'Always-on action (overrides toggle)',
          },
        },
      },
    ],
    () => ({ enabled: commonEnabled }),
  )

  // Dynamic demo
  interface DynamicShortcut {
    id: number
    hotkey: string
    label: string
    description: string
    count: number
  }

  let nextId = 0
  let shortcuts = $state<Array<DynamicShortcut>>([
    {
      id: nextId++,
      hotkey: 'Shift+A',
      label: 'Action A',
      description: 'First dynamic action',
      count: 0,
    },
    {
      id: nextId++,
      hotkey: 'Shift+B',
      label: 'Action B',
      description: 'Second dynamic action',
      count: 0,
    },
    {
      id: nextId++,
      hotkey: 'Shift+C',
      label: 'Action C',
      description: 'Third dynamic action',
      count: 0,
    },
  ])

  let newHotkey = $state('')
  let newLabel = $state('')
  let newDescription = $state('')

  createHotkeys(() =>
    shortcuts.map(
      (s): CreateHotkeyDefinition => ({
        hotkey: s.hotkey as Hotkey,
        callback: () => {
          shortcuts = shortcuts.map((item) =>
            item.id === s.id ? { ...item, count: item.count + 1 } : item,
          )
        },
        options: {
          meta: { name: s.label, description: s.description },
        },
      }),
    ),
  )

  function addShortcut() {
    const trimmed = newHotkey.trim()
    if (!trimmed || !newLabel.trim()) return
    shortcuts = [
      ...shortcuts,
      {
        id: nextId++,
        hotkey: trimmed,
        label: newLabel.trim(),
        description: newDescription.trim(),
        count: 0,
      },
    ]
    newHotkey = ''
    newLabel = ''
    newDescription = ''
  }

  function removeShortcut(id: number) {
    shortcuts = shortcuts.filter((s) => s.id !== id)
  }

  function fd(h: string) {
    return formatForDisplay(h as Hotkey)
  }

  // Registrations viewer
  const registrations = getHotkeyRegistrations()
</script>

<div class="app">
  <header>
    <h1>createHotkeys</h1>
    <p>
      Register multiple hotkeys in a single call. Supports dynamic arrays for
      variable-length shortcut lists.
    </p>
  </header>

  <!-- Basic Multi-Hotkey -->
  <div class="demo-section">
    <h2>Basic Multi-Hotkey Registration</h2>
    <p>
      All three hotkeys are registered in a single <code>createHotkeys()</code>
      call with <code>meta</code> for name and description.
    </p>
    <div class="hotkey-grid">
      <div>
        <kbd>{fd('Shift+S')}</kbd> Save ({saveCount})
      </div>
      <div>
        <kbd>{fd('Shift+U')}</kbd> Undo ({undoCount})
      </div>
      <div>
        <kbd>{fd('Shift+R')}</kbd> Redo ({redoCount})
      </div>
    </div>
    {#if log.length > 0}
      <div class="log">
        {#each log as entry}
          <div class="log-entry">{entry}</div>
        {/each}
      </div>
    {/if}
    <pre class="code-block">{`createHotkeys([
  {
    hotkey: 'Shift+S',
    callback: () => save(),
    options: { meta: { name: 'Save', description: 'Save the document' } },
  },
  {
    hotkey: 'Shift+U',
    callback: () => undo(),
    options: { meta: { name: 'Undo', description: 'Undo the last action' } },
  },
])`}</pre>
  </div>

  <!-- Common Options -->
  <div class="demo-section">
    <h2>Common Options with Per-Hotkey Overrides</h2>
    <p>
      <kbd>{fd('Alt+J')}</kbd> and
      <kbd>{fd('Alt+K')}</kbd> respect the global toggle.
      <kbd>{fd('Alt+L')}</kbd> overrides
      <code>enabled: true</code> so it always works.
    </p>
    <div style="margin-bottom: 12px">
      <button onclick={() => (commonEnabled = !commonEnabled)}>
        {commonEnabled ? 'Disable' : 'Enable'} common hotkeys
      </button>
    </div>
    <div class="hotkey-grid">
      <div>
        <kbd>{fd('Alt+J')}</kbd> Action A ({counts.a})
      </div>
      <div>
        <kbd>{fd('Alt+K')}</kbd> Action B ({counts.b})
      </div>
      <div>
        <kbd>{fd('Alt+L')}</kbd> Action C ({counts.c})
        <span class="hint"> (always on)</span>
      </div>
    </div>
    <pre class="code-block">{`createHotkeys(
  [
    { hotkey: 'Alt+J', callback: () => actionA(),
      options: { meta: { name: 'Action A' } } },
    { hotkey: 'Alt+L', callback: () => actionC(),
      options: { enabled: true, meta: { name: 'Action C' } } },
  ],
  () => ({ enabled }), // common option
)`}</pre>
  </div>

  <!-- Dynamic -->
  <div class="demo-section">
    <h2>Dynamic Hotkey List</h2>
    <p>
      Add or remove hotkeys at runtime. Because <code>createHotkeys</code>
      accepts a dynamic array, this works with Svelte's reactivity.
    </p>
    <div class="dynamic-list">
      {#each shortcuts as s (s.id)}
        <div class="dynamic-item">
          <kbd>{fd(s.hotkey)}</kbd>
          <span>{s.label}</span>
          <span class="count">{s.count}</span>
          <button onclick={() => removeShortcut(s.id)}>Remove</button>
        </div>
      {/each}
      {#if shortcuts.length === 0}
        <p class="hint">No shortcuts registered. Add one below.</p>
      {/if}
    </div>
    <div class="add-form">
      <input
        type="text"
        placeholder="Hotkey (e.g. Shift+D)"
        bind:value={newHotkey}
        onkeydown={(e) => {
          if (e.key === 'Enter') addShortcut()
        }}
      />
      <input
        type="text"
        placeholder="Name (e.g. Action D)"
        bind:value={newLabel}
        onkeydown={(e) => {
          if (e.key === 'Enter') addShortcut()
        }}
      />
      <input
        type="text"
        placeholder="Description (optional)"
        bind:value={newDescription}
        onkeydown={(e) => {
          if (e.key === 'Enter') addShortcut()
        }}
      />
      <button onclick={addShortcut} disabled={!newHotkey || !newLabel}>
        Add
      </button>
    </div>
    <pre class="code-block">{`let shortcuts = $state([...])

createHotkeys(
  () => shortcuts.map((s) => ({
    hotkey: s.key,
    callback: s.action,
    options: { meta: { name: s.name, description: s.description } },
  })),
)`}</pre>
  </div>

  <!-- Registrations Viewer -->
  <div class="demo-section">
    <h2>Live Registrations (getHotkeyRegistrations)</h2>
    <p>
      This table is rendered from
      <code>getHotkeyRegistrations()</code> — a reactive view of all registered hotkeys.
      It updates automatically as hotkeys are added, removed, enabled/disabled, or
      triggered.
    </p>
    <table class="registrations-table">
      <thead>
        <tr>
          <th>Hotkey</th>
          <th>Name</th>
          <th>Description</th>
          <th>Enabled</th>
          <th>Triggers</th>
        </tr>
      </thead>
      <tbody>
        {#each registrations.hotkeys as reg (reg.id)}
          <tr>
            <td>
              <kbd>{formatForDisplay(reg.hotkey)}</kbd>
            </td>
            <td>{reg.options.meta?.name ?? '—'}</td>
            <td class="description-cell">
              {reg.options.meta?.description ?? '—'}
            </td>
            <td>
              <span
                class={reg.options.enabled !== false
                  ? 'status-on'
                  : 'status-off'}
              >
                {reg.options.enabled !== false ? 'yes' : 'no'}
              </span>
            </td>
            <td class="trigger-count">{reg.triggerCount}</td>
          </tr>
        {/each}
        {#if registrations.hotkeys.length === 0}
          <tr>
            <td colspan="5" class="hint">No hotkeys registered</td>
          </tr>
        {/if}
      </tbody>
    </table>
    {#if registrations.sequences.length > 0}
      <h3 style="margin-top: 16px">Sequences</h3>
      <table class="registrations-table">
        <thead>
          <tr>
            <th>Sequence</th>
            <th>Name</th>
            <th>Description</th>
            <th>Triggers</th>
          </tr>
        </thead>
        <tbody>
          {#each registrations.sequences as reg (reg.id)}
            <tr>
              <td>
                {#each reg.sequence as s, i}
                  {#if i > 0}{' '}{/if}<kbd>{formatForDisplay(s)}</kbd>
                {/each}
              </td>
              <td>{reg.options.meta?.name ?? '—'}</td>
              <td class="description-cell">
                {reg.options.meta?.description ?? '—'}
              </td>
              <td class="trigger-count">{reg.triggerCount}</td>
            </tr>
          {/each}
        </tbody>
      </table>
    {/if}
    <pre class="code-block">{`const registrations = getHotkeyRegistrations()

// Render a live table of all registrations
// registrations.hotkeys → reactive array
// registrations.hotkeys[0].options.meta?.name
// registrations.hotkeys[0].triggerCount`}</pre>
  </div>
</div>