TanStack Hotkeys

repository·main·Indexed 20 days ago

https://github.com/tanstack/hotkeys

A library for creating type-safe keyboard shortcuts for web applications, featuring cross-platform modifier key support and advanced sequence handling. It provides framework-specific adapters for React, Preact, Solid, Vue, and Angular, along with dedicated devtools for real-time monitoring of registered hotkeys, held keys display, and programmatic triggering.

Tokens
140.8K
Snippets
506
Records
643
Agent score
67%

What's inside TanStack Hotkeys

  1. Overview of TanStack Hotkeys

    main

    TanStack Hotkeys is a type-safe, framework-agnostic library designed for managing keyboard shortcuts. It handles the complexities of cross-platform compatibility, keyboard layouts, and conflicting scopes.

    Key capabilities include:

    • Hotkey Registration: Centralized management with conflict detection and automatic input filtering (e.g., ignoring hotkeys when an input is focused).
    • Multi-Key Sequences: Support for Vim-style sequences (e.g., ['G', 'G']) with configurable timeouts.
    • Hotkey Recording: Tools for capturing custom shortcuts in user settings interfaces.
    • Key State Tracking: Hooks for monitoring held keys in real-time.
    • Platform-Aware Formatting: Automatically formats hotkey strings for display (e.g., ⌘⇧S on macOS vs Ctrl+Shift+S on Windows).
    • Framework Support: Provides adapters for React, Preact, Solid, Angular, Vue, and Lit.
  2. Understand the RegisterableHotkey type

    main

    The RegisterableHotkey type defines the valid inputs that can be passed to HotkeyManager.register() or the useHotkey() hook. It is a union type that allows for two ways of defining a hotkey:

    1. Hotkey: A type-safe string representation of a hotkey.
    2. RawHotkey: A raw object representation of a hotkey.

    Use this type when you need to define variables or function parameters that will eventually be used to register hotkeys within the TanStack Hotkeys system.

    type RegisterableHotkey = Hotkey | RawHotkey;
  3. How sequence matching works

    main

    The SequenceManager (a singleton) tracks all registered sequences. When a key is pressed:

    1. It checks if the key matches the next expected step in any registered sequence.
    2. If it matches, the sequence advances.
    3. If the timeout expires before the next key, the sequence resets.
    4. When all steps are completed, the callback fires.

    Overlapping Sequences: The manager tracks progress for multiple sequences independently. This allows you to register sequences that share the same prefix (e.g., ['D', 'D'] and ['D', 'W']) without conflict.

  4. HotkeySequenceRecorder input behavior

    main

    The following table describes how specific inputs affect the recording process:

    InputBehavior
    Valid chordAppended to steps; listener stays active
    Enter (no modifiers), commitKeys: 'enter', steps.length >= 1Commits and calls onRecord
    EscapeCancels; calls onCancel
    Backspace / Delete (no modifiers)Removes last step, or if empty runs onClear + onRecord([]) and stops

    Recorded chords use the portable Mod format.

  5. How sequences and modifiers work

    main

    TanStack Hotkeys supports multi-key sequences where keys are pressed one after another.

    Modifiers

    Each step in a sequence can include modifiers (e.g., ['Mod+K', 'Mod+C'] or ['G', 'Shift+G']). You can repeat modifiers across consecutive steps.

    Modifier-only keys

    While a sequence is in progress, modifier-only keydown events (Shift, Control, Alt, or Meta pressed alone) are ignored. They do not advance the sequence and they do not reset progress. This allows users to hold or tap a modifier between chords without breaking the sequence.

    Overlapping sequences

    The SequenceManager tracks progress for multiple sequences independently. If multiple sequences share a prefix (e.g., ['D', 'D'] and ['D', 'W']), the manager waits for the next key to determine which sequence to complete.

  6. Use modifier chords in sequences

    main

    Each step in a sequence can include modifiers (e.g., Shift, Control, Alt, Meta). You can use the same modifier on consecutive steps.

    While a sequence is in progress, modifier-only keydown events (pressing a modifier key without any other key) are ignored; they do not advance the sequence or reset progress, allowing users to hold or tap modifiers between steps without breaking the flow.

    // Example of using modifiers in consecutive steps
    useHotkeySequence(['Shift+R', 'Shift+T'], () => doNextAction())
  7. Understand hotkey recording behavior and key mappings

    main

    The recorder follows specific rules for different key types:

    KeyBehavior
    Modifier only (Shift, Ctrl, etc.)Waits for a non-modifier key -- modifier-only presses don't complete a recording
    Modifier + key (e.g., Ctrl+S)Records the full combination
    Single key (e.g., Escape, F1)Records the single key
    EscapeCancels the recording
    Backspace / DeleteClears the currently recorded hotkey

    Mod Auto-Conversion

    Recorded hotkeys automatically use the portable Mod format. For example, if a user on macOS presses Command+S, the recorded hotkey will be Mod+S instead of Meta+S. This ensures shortcuts remain portable across different operating systems.

  8. Use chained modifier chords in sequences

    main

    You can include modifiers (like Shift) in consecutive steps of a sequence.

    Note on Modifier-only keys: While a sequence is in progress, pressing a modifier key (Shift, Control, Alt, or Meta) without another key will not advance the sequence or reset progress. This allows users to press Shift alone between chords without breaking the sequence.

    injectHotkeySequence(['Shift+R', 'Shift+T'], () => doNextAction())
  9. How the Sequence Manager handles overlapping sequences

    main

    The SequenceManager tracks progress for multiple sequences independently. If multiple sequences share the same prefix (e.g., ['D', 'D'] and ['D', 'W']), the manager waits for the subsequent key to determine which sequence to complete. It checks if the key matches the next expected step, advances the sequence if it matches, or resets if the timeout expires.

    // Both share the 'D' prefix
    useHotkeySequence(['D', 'D'], () => deleteLine())
    useHotkeySequence(['D', 'W'], () => deleteWord())
    useHotkeySequence(['D', 'I', 'W'], () => deleteInnerWord())
  10. Use the ParsedHotkey interface for dynamic hotkeys

    main

    The ParsedHotkey interface provides a way to represent a hotkey as an object instead of a string. This is useful as a fallback when the standard type-safe Hotkey string union doesn't fit your use case, such as when handling dynamic user input or complex scenarios. You can pass a ParsedHotkey object directly to hotkey functions (like useHotkey) instead of a string.

    // Type-safe hotkey string
    useHotkey('Mod+S', handler)
    
    // Fallback: parsed hotkey for dynamic scenarios
    const parsed = parseHotkey(userInput)
    useHotkey(parsed, handler) // Works even if userInput isn't in Hotkey type
  11. Use modifiers in sequences

    main

    Each step in a sequence can include modifiers (e.g., Shift, Control, Alt, Meta). You can also create chained modifier chords where the same modifier is repeated across consecutive steps.

    Note on Modifier-only keys: While a sequence is in progress, pressing a modifier key alone (without another key) is ignored. It will not advance the sequence, nor will it reset progress. This allows users to hold or tap modifiers between steps without breaking the sequence.

    // Modifiers on steps
    createHotkeySequence(['Mod+K', 'Mod+C'], () => commentSelection())
    
    // Chained modifiers
    createHotkeySequence(['Shift+R', 'Shift+T'], () => doNextAction())