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