Skip to content

Octane Example: UseHeldKeys

import { createRoot, useEffect, useState } from 'octane'

import {
  HotkeysProvider,
  formatForDisplay,
  useHeldKeyCodes,
  useHeldKeys,
} from '@tanstack/octane-hotkeys'

import './index.css'

function App() @{
  const heldKeys = useHeldKeys()
  const heldCodes = useHeldKeyCodes()
  const [history, setHistory] = useState<Array<string>>([])
  useEffect(() => {
    if (heldKeys.length > 0) {
      const combo = heldKeys
        .map((k) => formatForDisplay(k, { useSymbols: true }))
        .join(' + ')
      setHistory((h) => {
        // Only add if different from last entry
        if (h[h.length - 1] !== combo) {
          return [...h.slice(-9), combo]
        }
        return h
      })
    }
  }, [heldKeys])
  <div className="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 className="demo-section">
            <h2>Currently Held Keys</h2>
            <div className="key-display">
              @if (heldKeys.length > 0) { @for (const [index, key] of Array.from(heldKeys.entries()); key key) { const code = heldCodes[key]
  <>
                      @if (index > 0) { <span className="plus">+</span> }
                      <kbd className="large">
                        {formatForDisplay(key, { useSymbols: true })}
                        @if (code && code !== key) { <small className="code-label">{code}</small> }
                      </kbd>
                    </> } } @else { <span className="placeholder">Press any keys...</span> }
            </div>
            <div className="stats">
              Keys held: <strong>{heldKeys.length}</strong>
            </div>
          </section>

          <section className="demo-section">
            <h2>Usage</h2>
            <pre className="code-block">{`import { useHeldKeys } from '@tanstack/octane-hotkeys'

function KeyDisplay() @{
  const heldKeys = useHeldKeys()

  <div>Currently pressed: {heldKeys.join(' + ') || 'None'}</div>
}`}</pre>
          </section>

          <section className="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 className="demo-section">
            <h2>Recent Combinations</h2>
            @if (history.length > 0) { <ul className="history-list">
                @for (const [i, combo] of Array.from(history.entries()); key i) {
  <li>{combo}</li> }
              </ul> } @else { <p className="placeholder">Press some key combinations...</p> }
            <button onClick={() => setHistory([])}>Clear History</button>
          </section>

          <section className="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>
}

function Root() @{
<HotkeysProvider><App /></HotkeysProvider>
}

const root = createRoot(document.getElementById('root')!)
root.render(Root)
import.meta.hot?.dispose(() => root.unmount())