CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-hotkeys-hook

React hook for handling keyboard shortcuts in components in a declarative way

Pending
Overview
Eval results
Files

key-checking.mddocs/

Real-time Key Checking

Utility function for checking if specified keys are currently pressed, enabling conditional logic based on keyboard state.

Capabilities

isHotkeyPressed Function

Function to check if specific keys are currently being pressed.

/**
 * Check if specified keys are currently pressed
 * @param key - Single key string or array of key strings to check
 * @param delimiter - Character used to separate multiple keys in a string (default: ',')
 * @returns True if all specified keys are currently pressed
 */
function isHotkeyPressed(key: string | readonly string[], delimiter?: string): boolean;

Usage Examples:

import { isHotkeyPressed } from 'react-hotkeys-hook';

// Check single key
if (isHotkeyPressed('shift')) {
  console.log('Shift is currently pressed');
}

// Check key combination (all keys must be pressed)
if (isHotkeyPressed('ctrl+k')) {
  console.log('Ctrl+K is currently pressed');
}

// Check multiple possible combinations (any can be pressed)
if (isHotkeyPressed(['ctrl+k', 'cmd+k'])) {
  console.log('Either Ctrl+K or Cmd+K is pressed');
}

// Check with custom delimiter
if (isHotkeyPressed('ctrl,shift,k', ',')) {
  console.log('Ctrl, Shift, and K are all pressed');
}

Practical Examples

Conditional UI Rendering

function ContextualHelpButton() {
  const [showHelp, setShowHelp] = useState(false);
  
  useEffect(() => {
    const checkKeys = () => {
      // Show help when F1 is pressed
      if (isHotkeyPressed('f1')) {
        setShowHelp(true);
      } else {
        setShowHelp(false);
      }
    };
    
    // Check key state on each frame
    const interval = setInterval(checkKeys, 16); // ~60fps
    
    return () => clearInterval(interval);
  }, []);
  
  return (
    <div>
      {showHelp && (
        <div className="help-overlay">
          <p>Help is active while F1 is held</p>
        </div>
      )}
    </div>
  );
}

Advanced Modifier Key Logic

function AdvancedSelector() {
  const [selectedItems, setSelectedItems] = useState(new Set());
  
  const handleItemClick = (itemId) => {
    setSelectedItems(prev => {
      const newSet = new Set(prev);
      
      if (isHotkeyPressed('ctrl') || isHotkeyPressed('cmd')) {
        // Multi-select: toggle item
        if (newSet.has(itemId)) {
          newSet.delete(itemId);
        } else {
          newSet.add(itemId);
        }
      } else if (isHotkeyPressed('shift')) {
        // Range select: select from last to current
        // Implementation for range selection
        return handleRangeSelect(itemId, prev);
      } else {
        // Single select: replace selection
        newSet.clear();
        newSet.add(itemId);
      }
      
      return newSet;
    });
  };
  
  return (
    <div className="item-list">
      {items.map(item => (
        <div
          key={item.id}
          className={selectedItems.has(item.id) ? 'selected' : ''}
          onClick={() => handleItemClick(item.id)}
        >
          {item.name}
        </div>
      ))}
    </div>
  );
}

Dynamic Hotkey Descriptions

function HotkeyReference() {
  const [currentModifiers, setCurrentModifiers] = useState([]);
  
  useEffect(() => {
    const updateModifiers = () => {
      const modifiers = [];
      if (isHotkeyPressed('ctrl')) modifiers.push('Ctrl');
      if (isHotkeyPressed('shift')) modifiers.push('Shift');
      if (isHotkeyPressed('alt')) modifiers.push('Alt');
      if (isHotkeyPressed('meta')) modifiers.push('Cmd');
      
      setCurrentModifiers(modifiers);
    };
    
    const interval = setInterval(updateModifiers, 50);
    return () => clearInterval(interval);
  }, []);
  
  const getHotkeyDescription = (baseKey, description) => {
    const modifierStr = currentModifiers.length > 0 
      ? currentModifiers.join('+') + '+' 
      : '';
    return `${modifierStr}${baseKey} - ${description}`;
  };
  
  return (
    <div className="hotkey-reference">
      <h3>Available Hotkeys</h3>
      {currentModifiers.length > 0 && (
        <p>Currently holding: {currentModifiers.join(', ')}</p>
      )}
      
      <ul>
        <li>{getHotkeyDescription('K', 'Open command palette')}</li>
        <li>{getHotkeyDescription('S', 'Save document')}</li>
        <li>{getHotkeyDescription('Z', 'Undo action')}</li>
      </ul>
    </div>
  );
}

Game-like Input Handling

function MovementController() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [isMoving, setIsMoving] = useState(false);
  
  useEffect(() => {
    const updateMovement = () => {
      let deltaX = 0;
      let deltaY = 0;
      let moving = false;
      
      // Basic movement
      if (isHotkeyPressed('w') || isHotkeyPressed('arrowup')) {
        deltaY -= 1;
        moving = true;
      }
      if (isHotkeyPressed('s') || isHotkeyPressed('arrowdown')) {
        deltaY += 1;
        moving = true;
      }
      if (isHotkeyPressed('a') || isHotkeyPressed('arrowleft')) {
        deltaX -= 1;
        moving = true;
      }
      if (isHotkeyPressed('d') || isHotkeyPressed('arrowright')) {
        deltaX += 1;
        moving = true;
      }
      
      // Speed modifier
      const speed = isHotkeyPressed('shift') ? 2 : 1;
      
      if (moving) {
        setPosition(prev => ({
          x: prev.x + deltaX * speed,
          y: prev.y + deltaY * speed
        }));
      }
      
      setIsMoving(moving);
    };
    
    const interval = setInterval(updateMovement, 16); // 60fps
    return () => clearInterval(interval);
  }, []);
  
  return (
    <div className="movement-controller">
      <div 
        className={`player ${isMoving ? 'moving' : ''}`}
        style={{
          transform: `translate(${position.x}px, ${position.y}px)`
        }}
      />
      <div className="status">
        Position: ({position.x}, {position.y})
        {isHotkeyPressed('shift') && ' (Fast mode)'}
      </div>
    </div>
  );
}

Cross-platform Key Checking

function CrossPlatformActions() {
  const handleAction = (action) => {
    // Check for platform-specific modifier keys
    const cmdKey = isHotkeyPressed(['cmd', 'ctrl']); // Cmd on Mac, Ctrl elsewhere
    const optionKey = isHotkeyPressed(['alt', 'option']); // Alt/Option
    
    if (cmdKey && isHotkeyPressed('s')) {
      handleSave();
    } else if (cmdKey && isHotkeyPressed('z')) {
      handleUndo();
    } else if (cmdKey && optionKey && isHotkeyPressed('i')) {
      handleDevTools();
    }
  };
  
  return (
    <div>
      <p>Cross-platform shortcuts available:</p>
      <ul>
        <li>Cmd/Ctrl + S: Save</li>
        <li>Cmd/Ctrl + Z: Undo</li>
        <li>Cmd/Ctrl + Alt/Option + I: Dev Tools</li>
      </ul>
    </div>
  );
}

Debugging Key States

function KeyStateDebugger() {
  const [pressedKeys, setPressedKeys] = useState([]);
  
  useEffect(() => {
    const commonKeys = [
      'ctrl', 'shift', 'alt', 'meta', 'cmd',
      'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
      'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
      '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
      'enter', 'escape', 'space', 'tab', 'backspace'
    ];
    
    const checkKeys = () => {
      const pressed = commonKeys.filter(key => isHotkeyPressed(key));
      setPressedKeys(pressed);
    };
    
    const interval = setInterval(checkKeys, 50);
    return () => clearInterval(interval);
  }, []);
  
  return (
    <div className="key-debugger">
      <h3>Currently Pressed Keys</h3>
      <div className="pressed-keys">
        {pressedKeys.length > 0 ? (
          pressedKeys.map(key => (
            <span key={key} className="key-badge">{key}</span>
          ))
        ) : (
          <span className="no-keys">No keys pressed</span>
        )}
      </div>
      
      <div className="combinations">
        <h4>Active Combinations</h4>
        {isHotkeyPressed('ctrl+k') && <div>Ctrl+K is active</div>}
        {isHotkeyPressed('shift+ctrl') && <div>Shift+Ctrl is active</div>}
        {isHotkeyPressed(['cmd+s', 'ctrl+s']) && <div>Save combination is active</div>}
      </div>
    </div>
  );
}

Implementation Details

Key State Tracking

The function tracks key states using a global set that is automatically maintained:

  • Keys are added to the set on keydown events
  • Keys are removed from the set on keyup events
  • The set is cleared on window blur to handle edge cases
  • Special handling for macOS Meta key behavior

Key Normalization

All keys are normalized to lowercase and common key codes are mapped to consistent names:

  • escescape
  • returnenter
  • Arrow keys: leftarrowleft, etc.
  • Modifier keys are consistently named across platforms

Performance Considerations

  • The key state checking is very fast (O(1) for each key lookup)
  • No event listeners are added by calling this function
  • Safe to call frequently (e.g., in animation loops)
  • Memory usage is minimal and constant

Install with Tessl CLI

npx tessl i tessl/npm-react-hotkeys-hook

docs

index.md

key-checking.md

key-recording.md

main-hook.md

scope-management.md

tile.json