Docs
CodeRabbit
Cloudflare
AG Grid
SerpAPI
Netlify
OpenRouter
Neon
WorkOS
Clerk
Convex
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
CodeRabbit
Cloudflare
AG Grid
SerpAPI
Netlify
OpenRouter
Neon
WorkOS
Clerk
Convex
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
API Reference
Hotkeys API Reference
Hotkey Sequence API Reference
Key Hold API Reference
Held Keys API Reference
Hotkey Recorder API Reference
Format for Display API Reference

Svelte Example: Create Hotkey

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

  let lastHotkey = $state<Hotkey | null>(null)
  let saveCount = $state(0)
  let incrementCount = $state(0)
  let enabled = $state(true)
  let activeTab = $state(1)
  let navigationCount = $state(0)
  let functionKeyCount = $state(0)
  let multiModifierCount = $state(0)
  let editingKeyCount = $state(0)

  let modalOpen = $state(false)
  let editorContent = $state('')
  let sidebarShortcutCount = $state(0)
  let modalShortcutCount = $state(0)
  let editorShortcutCount = $state(0)

  let modalRef = $state<HTMLDivElement | null>(null)

  $effect(() => {
    if (modalOpen && modalRef) {
      modalRef.focus()
    }
  })

  createHotkey('Mod+S', (_event, { hotkey }) => {
    lastHotkey = hotkey
    saveCount++
    console.log('Hotkey triggered:', hotkey)
  })

  createHotkey(
    'Mod+K',
    (_event, { hotkey }) => {
      lastHotkey = hotkey
      incrementCount++
    },
    { requireReset: true },
  )

  createHotkey(
    'Mod+E',
    () => {
      lastHotkey = 'Mod+E'
      alert('This hotkey can be toggled!')
    },
    () => ({ enabled }),
  )

  createHotkey('Mod+1', () => {
    lastHotkey = 'Mod+1'
    activeTab = 1
  })
  createHotkey('Mod+2', () => {
    lastHotkey = 'Mod+2'
    activeTab = 2
  })
  createHotkey('Mod+3', () => {
    lastHotkey = 'Mod+3'
    activeTab = 3
  })
  createHotkey('Mod+4', () => {
    lastHotkey = 'Mod+4'
    activeTab = 4
  })
  createHotkey('Mod+5', () => {
    lastHotkey = 'Mod+5'
    activeTab = 5
  })

  createHotkey('Shift+ArrowUp', () => {
    lastHotkey = 'Shift+ArrowUp'
    navigationCount++
  })
  createHotkey('Shift+ArrowDown', () => {
    lastHotkey = 'Shift+ArrowDown'
    navigationCount++
  })
  createHotkey('Alt+ArrowLeft', () => {
    lastHotkey = 'Alt+ArrowLeft'
    navigationCount++
  })
  createHotkey('Alt+ArrowRight', () => {
    lastHotkey = 'Alt+ArrowRight'
    navigationCount++
  })
  createHotkey('Mod+Home', () => {
    lastHotkey = 'Mod+Home'
    navigationCount++
  })
  createHotkey('Mod+End', () => {
    lastHotkey = 'Mod+End'
    navigationCount++
  })
  createHotkey('Control+PageUp', () => {
    lastHotkey = 'Control+PageUp'
    navigationCount++
  })
  createHotkey('Control+PageDown', () => {
    lastHotkey = 'Control+PageDown'
    navigationCount++
  })

  createHotkey('Meta+F4', () => {
    lastHotkey = 'Alt+F4'
    functionKeyCount++
    alert('Alt+F4 pressed (normally closes window)')
  })
  createHotkey('Control+F5', () => {
    lastHotkey = 'Control+F5'
    functionKeyCount++
  })
  createHotkey('Mod+F1', () => {
    lastHotkey = 'Mod+F1'
    functionKeyCount++
  })
  createHotkey('Shift+F10', () => {
    lastHotkey = 'Shift+F10'
    functionKeyCount++
  })

  createHotkey('Mod+Shift+S', () => {
    lastHotkey = 'Mod+Shift+S'
    multiModifierCount++
  })
  createHotkey('Mod+Shift+Z', () => {
    lastHotkey = 'Mod+Shift+Z'
    multiModifierCount++
  })
  createHotkey({ key: 'A', ctrl: true, alt: true }, () => {
    lastHotkey = 'Control+Alt+A'
    multiModifierCount++
  })
  createHotkey('Control+Shift+N', () => {
    lastHotkey = 'Control+Shift+N'
    multiModifierCount++
  })
  createHotkey('Mod+Alt+T', () => {
    lastHotkey = 'Mod+Alt+T'
    multiModifierCount++
  })
  createHotkey('Control+Alt+Shift+X', () => {
    lastHotkey = 'Control+Alt+Shift+X'
    multiModifierCount++
  })

  createHotkey('Mod+Enter', () => {
    lastHotkey = 'Mod+Enter'
    editingKeyCount++
  })
  createHotkey('Shift+Enter', () => {
    lastHotkey = 'Shift+Enter'
    editingKeyCount++
  })
  createHotkey('Mod+Backspace', () => {
    lastHotkey = 'Mod+Backspace'
    editingKeyCount++
  })
  createHotkey('Mod+Delete', () => {
    lastHotkey = 'Mod+Delete'
    editingKeyCount++
  })
  createHotkey('Control+Tab', () => {
    lastHotkey = 'Control+Tab'
    editingKeyCount++
  })
  createHotkey('Shift+Tab', () => {
    lastHotkey = 'Shift+Tab'
    editingKeyCount++
  })
  createHotkey('Mod+Space', () => {
    lastHotkey = 'Mod+Space'
    editingKeyCount++
  })

  createHotkey({ key: 'Escape' }, () => {
    lastHotkey = null
    saveCount = 0
    incrementCount = 0
    navigationCount = 0
    functionKeyCount = 0
    multiModifierCount = 0
    editingKeyCount = 0
    activeTab = 1
  })

  createHotkey('F12', () => {
    lastHotkey = 'F12'
    functionKeyCount++
  })

  const sidebarBoldHotkey = createHotkeyAttachment('Mod+B', () => {
    lastHotkey = 'Mod+B'
    sidebarShortcutCount++
    alert(
      'Sidebar shortcut triggered! This only works when the sidebar area is focused.',
    )
  })

  const sidebarNewItemHotkey = createHotkeyAttachment('Mod+N', () => {
    lastHotkey = 'Mod+N'
    sidebarShortcutCount++
  })

  const closeModalHotkey = createHotkeyAttachment(
    'Escape',
    () => {
      lastHotkey = 'Escape'
      modalShortcutCount++
      modalOpen = false
    },
    () => ({ enabled: modalOpen }),
  )

  const submitModalHotkey = createHotkeyAttachment(
    'Mod+Enter',
    () => {
      lastHotkey = 'Mod+Enter'
      modalShortcutCount++
      alert('Modal submit shortcut!')
    },
    () => ({ enabled: modalOpen }),
  )

  const saveEditorHotkey = createHotkeyAttachment('Mod+S', () => {
    lastHotkey = 'Mod+S'
    editorShortcutCount++
    alert(
      `Editor content saved: "${editorContent.substring(0, 50)}${editorContent.length > 50 ? '...' : ''}"`,
    )
  })

  const addEditorCommentHotkey = createHotkeyAttachment('Mod+/', () => {
    lastHotkey = 'Mod+/'
    editorShortcutCount++
    editorContent += '\n// Comment added via shortcut'
  })

  const clearEditorHotkey = createHotkeyAttachment('Mod+K', () => {
    lastHotkey = 'Mod+K'
    editorShortcutCount++
    editorContent = ''
  })
</script>

<div class="app">
  <header>
    <h1>createHotkey</h1>
    <p>
      Register keyboard shortcuts with callback context containing the hotkey
      and parsed hotkey information.
    </p>
  </header>

  <main>
    <section class="demo-section">
      <h2>Basic Hotkey</h2>
      <p>
        Press <kbd>{formatForDisplay('Mod+S')}</kbd> to trigger
      </p>
      <div class="counter">Save triggered: {saveCount}x</div>
      <pre
        class="code-block">{`createHotkey('Mod+S', (_event, { hotkey, parsedHotkey }) => {
  console.log('Hotkey:', hotkey)
  console.log('Parsed:', parsedHotkey)
})`}</pre>
    </section>

    <section class="demo-section">
      <h2>With requireReset</h2>
      <p>
        Hold <kbd>{formatForDisplay('Mod+K')}</kbd> — only increments once until you
        release all keys
      </p>
      <div class="counter">Increment: {incrementCount}</div>
      <p class="hint">
        This prevents repeated triggering while holding the keys down. Release
        all keys to allow re-triggering.
      </p>
      <pre class="code-block">{`createHotkey(
  'Mod+K',
  (event, { hotkey }) => {
    count++
  },
  { requireReset: true }
)`}</pre>
    </section>

    <section class="demo-section">
      <h2>Conditional Hotkey</h2>
      <p>
        <kbd>{formatForDisplay('Mod+E')}</kbd> is currently
        <strong>{enabled ? 'enabled' : 'disabled'}</strong>
      </p>
      <button onclick={() => (enabled = !enabled)}>
        {enabled ? 'Disable' : 'Enable'} Hotkey
      </button>
      <pre class="code-block">{`let enabled = $state(true)

createHotkey(
  'Mod+E',
  (event, { hotkey }) => {
    alert('Triggered!')
  },
  () => ({ enabled })
)`}</pre>
    </section>

    <section class="demo-section">
      <h2>Number Key Combinations</h2>
      <p>Common for tab/section switching:</p>
      <div class="hotkey-grid">
        <div><kbd>{formatForDisplay('Mod+1')}</kbd> → Tab 1</div>
        <div><kbd>{formatForDisplay('Mod+2')}</kbd> → Tab 2</div>
        <div><kbd>{formatForDisplay('Mod+3')}</kbd> → Tab 3</div>
        <div><kbd>{formatForDisplay('Mod+4')}</kbd> → Tab 4</div>
        <div><kbd>{formatForDisplay('Mod+5')}</kbd> → Tab 5</div>
      </div>
      <div class="counter">Active Tab: {activeTab}</div>
      <pre class="code-block">{`createHotkey('Mod+1', () => activeTab = 1)
createHotkey('Mod+2', () => activeTab = 2)`}</pre>
    </section>

    <section class="demo-section">
      <h2>Navigation Key Combinations</h2>
      <p>Selection and navigation shortcuts:</p>
      <div class="hotkey-grid">
        <div><kbd>{formatForDisplay('Shift+ArrowUp')}</kbd> — Select up</div>
        <div>
          <kbd>{formatForDisplay('Shift+ArrowDown')}</kbd> — Select down
        </div>
        <div>
          <kbd>{formatForDisplay('Alt+ArrowLeft')}</kbd> — Navigate back
        </div>
        <div>
          <kbd>{formatForDisplay('Alt+ArrowRight')}</kbd> — Navigate forward
        </div>
        <div><kbd>{formatForDisplay('Mod+Home')}</kbd> — Go to start</div>
        <div><kbd>{formatForDisplay('Mod+End')}</kbd> — Go to end</div>
        <div>
          <kbd>{formatForDisplay('Control+PageUp')}</kbd> — Previous page
        </div>
        <div>
          <kbd>{formatForDisplay('Control+PageDown')}</kbd> — Next page
        </div>
      </div>
      <div class="counter">Navigation triggered: {navigationCount}x</div>
    </section>

    <section class="demo-section">
      <h2>Function Key Combinations</h2>
      <p>System and application shortcuts:</p>
      <div class="hotkey-grid">
        <div><kbd>{formatForDisplay('Alt+F4')}</kbd> — Close window</div>
        <div><kbd>{formatForDisplay('Control+F5')}</kbd> — Hard refresh</div>
        <div><kbd>{formatForDisplay('Mod+F1')}</kbd> — Help</div>
        <div><kbd>{formatForDisplay('Shift+F10')}</kbd> — Context menu</div>
        <div><kbd>{formatForDisplay('F12')}</kbd> — DevTools</div>
      </div>
      <div class="counter">Function keys triggered: {functionKeyCount}x</div>
    </section>

    <section class="demo-section">
      <h2>Multi-Modifier Combinations</h2>
      <p>Complex shortcuts with multiple modifiers:</p>
      <div class="hotkey-grid">
        <div><kbd>{formatForDisplay('Mod+Shift+S')}</kbd> — Save As</div>
        <div><kbd>{formatForDisplay('Mod+Shift+Z')}</kbd> — Redo</div>
        <div>
          <kbd>{formatForDisplay('Control+Alt+A')}</kbd> — Special action
        </div>
        <div>
          <kbd>{formatForDisplay('Control+Shift+N')}</kbd> — New incognito
        </div>
        <div><kbd>{formatForDisplay('Mod+Alt+T')}</kbd> — Toggle theme</div>
        <div>
          <kbd>{formatForDisplay('Control+Alt+Shift+X')}</kbd> — Triple modifier
        </div>
      </div>
      <div class="counter">
        Multi-modifier triggered: {multiModifierCount}x
      </div>
    </section>

    <section class="demo-section">
      <h2>Editing Key Combinations</h2>
      <p>Text editing and form shortcuts:</p>
      <div class="hotkey-grid">
        <div><kbd>{formatForDisplay('Mod+Enter')}</kbd> — Submit form</div>
        <div><kbd>{formatForDisplay('Shift+Enter')}</kbd> — New line</div>
        <div>
          <kbd>{formatForDisplay('Mod+Backspace')}</kbd> — Delete word
        </div>
        <div>
          <kbd>{formatForDisplay('Mod+Delete')}</kbd> — Delete forward
        </div>
        <div><kbd>{formatForDisplay('Control+Tab')}</kbd> — Next tab</div>
        <div><kbd>{formatForDisplay('Shift+Tab')}</kbd> — Previous field</div>
        <div><kbd>{formatForDisplay('Mod+Space')}</kbd> — Toggle</div>
      </div>
      <div class="counter">Editing keys triggered: {editingKeyCount}x</div>
    </section>

    {#if lastHotkey}
      <div class="info-box">
        <strong>Last triggered:</strong>
        {formatForDisplay(lastHotkey)}
      </div>
    {/if}

    <p class="hint">
      Press <kbd>Escape</kbd> to reset all counters
    </p>

    <section class="demo-section scoped-section">
      <h2>Scoped Keyboard Shortcuts</h2>
      <p>
        Shortcuts can be scoped to specific DOM elements using the
        <code>target</code> option. This allows different shortcuts to work in different
        parts of your application.
      </p>

      <div class="scoped-grid">
        <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
        <div
          class="scoped-area"
          tabindex="0"
          role="region"
          aria-label="Sidebar scoped area"
          {@attach sidebarBoldHotkey}
          {@attach sidebarNewItemHotkey}
        >
          <h3>Sidebar (Scoped Area)</h3>
          <p>Click here to focus, then try:</p>
          <div class="hotkey-list">
            <div>
              <kbd>{formatForDisplay('Mod+B')}</kbd> — Trigger sidebar action
            </div>
            <div><kbd>{formatForDisplay('Mod+N')}</kbd> — New item</div>
          </div>
          <div class="counter">
            Sidebar shortcuts: {sidebarShortcutCount}x
          </div>
          <p class="hint">
            These shortcuts only work when this sidebar area is focused or
            contains focus.
          </p>
        </div>

        <div class="scoped-area">
          <h3>Modal Dialog</h3>
          <button onclick={() => (modalOpen = true)}>Open Modal</button>
          {#if modalOpen}
            <!-- svelte-ignore a11y_click_events_have_key_events -->
            <div
              class="modal-overlay"
              onclick={() => (modalOpen = false)}
              role="button"
              tabindex="-1"
            >
              <!-- svelte-ignore a11y_click_events_have_key_events -->
              <div
                class="modal-content"
                bind:this={modalRef}
                tabindex="0"
                onclick={(e) => e.stopPropagation()}
                role="dialog"
                {@attach closeModalHotkey}
                {@attach submitModalHotkey}
              >
                <h3>Modal Dialog (Scoped)</h3>
                <p>Try these shortcuts while modal is open:</p>
                <div class="hotkey-list">
                  <div>
                    <kbd>{formatForDisplay('Escape')}</kbd> — Close modal
                  </div>
                  <div>
                    <kbd>{formatForDisplay('Mod+Enter')}</kbd> — Submit
                  </div>
                </div>
                <div class="counter">
                  Modal shortcuts: {modalShortcutCount}x
                </div>
                <p class="hint">
                  These shortcuts only work when the modal is open and focused.
                  The Escape key here won't conflict with the global Escape
                  handler.
                </p>
                <button onclick={() => (modalOpen = false)}>Close</button>
              </div>
            </div>
          {/if}
        </div>

        <div class="scoped-area">
          <h3>Text Editor (Scoped)</h3>
          <p>Focus the editor below and try:</p>
          <div class="hotkey-list">
            <div>
              <kbd>{formatForDisplay('Mod+S')}</kbd> — Save editor content
            </div>
            <div><kbd>{formatForDisplay('Mod+/')}</kbd> — Add comment</div>
            <div><kbd>{formatForDisplay('Mod+K')}</kbd> — Clear editor</div>
          </div>
          <textarea
            class="scoped-editor"
            bind:value={editorContent}
            placeholder="Focus here and try the shortcuts above..."
            rows="8"
            {@attach saveEditorHotkey}
            {@attach addEditorCommentHotkey}
            {@attach clearEditorHotkey}
          ></textarea>
          <div class="counter">Editor shortcuts: {editorShortcutCount}x</div>
          <p class="hint">
            These shortcuts only work when the editor is focused. Notice that <kbd
              >{formatForDisplay('Mod+S')}</kbd
            >
            here doesn't conflict with the global
            <kbd>{formatForDisplay('Mod+S')}</kbd>
            shortcut.
          </p>
        </div>
      </div>

      <pre class="code-block">{`const sidebarHotkey = createHotkeyAttachment(
  'Mod+B',
  () => console.log('Sidebar shortcut!'),
)

const modalEscape = createHotkeyAttachment(
  'Escape',
  () => (modalOpen = false),
  () => ({ enabled: modalOpen }),
)

const editorSave = createHotkeyAttachment('Mod+S', () => {
  saveEditorContent()
})`}</pre>
    </section>
  </main>
</div>