React hook for handling keyboard shortcuts in components in a declarative way
—
Hook for recording keyboard input to discover key combinations, useful for hotkey configuration interfaces and debugging.
Hook for recording keyboard input to capture key combinations as they are pressed.
/**
* Hook for recording keyboard input to discover key combinations
* @param useKey - Whether to record key names instead of key codes (default: false)
* @returns Tuple with recorded keys set and control functions
*/
function useRecordHotkeys(useKey?: boolean): [
Set<string>,
{
start: () => void;
stop: () => void;
resetKeys: () => void;
isRecording: boolean;
}
];Usage Examples:
import { useRecordHotkeys } from 'react-hotkeys-hook';
function HotkeyRecorder() {
const [keys, { start, stop, resetKeys, isRecording }] = useRecordHotkeys();
return (
<div>
<div>
Recorded keys: {Array.from(keys).join(' + ')}
</div>
<button onClick={start} disabled={isRecording}>
Start Recording
</button>
<button onClick={stop} disabled={!isRecording}>
Stop Recording
</button>
<button onClick={resetKeys}>
Clear Keys
</button>
<div>
Status: {isRecording ? 'Recording...' : 'Stopped'}
</div>
</div>
);
}Control whether to record key names or key codes using the useKey parameter.
// Record key codes (default) - more reliable across keyboards
const [codes, codeControls] = useRecordHotkeys(false);
// Example output: ['ControlLeft', 'KeyK']
// Record key names - more readable but less reliable
const [names, nameControls] = useRecordHotkeys(true);
// Example output: ['Control', 'k']function HotkeyConfig({ onSave }) {
const [keys, { start, stop, resetKeys, isRecording }] = useRecordHotkeys();
const [savedHotkey, setSavedHotkey] = useState('');
const handleSave = () => {
const combination = Array.from(keys).join('+');
setSavedHotkey(combination);
onSave(combination);
resetKeys();
};
return (
<div className="hotkey-config">
<h3>Configure Hotkey</h3>
<div className="recording-area">
<p>Press keys to record combination:</p>
<div className="key-display">
{keys.size > 0 ? Array.from(keys).join(' + ') : 'No keys recorded'}
</div>
</div>
<div className="controls">
<button onClick={isRecording ? stop : start}>
{isRecording ? 'Stop Recording' : 'Start Recording'}
</button>
<button onClick={resetKeys} disabled={keys.size === 0}>
Clear
</button>
<button onClick={handleSave} disabled={keys.size === 0}>
Save Hotkey
</button>
</div>
{savedHotkey && (
<div className="saved-hotkey">
Saved hotkey: <code>{savedHotkey}</code>
</div>
)}
</div>
);
}function HotkeyDebugger() {
const [keys, { start, stop, isRecording }] = useRecordHotkeys();
const [history, setHistory] = useState([]);
useEffect(() => {
// Auto-start recording for debugging
start();
return stop;
}, [start, stop]);
useEffect(() => {
if (keys.size > 0) {
const combination = Array.from(keys).join('+');
setHistory(prev => [
...prev.slice(-9), // Keep last 10 entries
{ keys: combination, timestamp: Date.now() }
]);
}
}, [keys]);
return (
<div className="hotkey-debugger">
<h3>Hotkey Debugger</h3>
<div className="current-keys">
<strong>Currently Pressed:</strong>
<code>{keys.size > 0 ? Array.from(keys).join(' + ') : 'None'}</code>
</div>
<div className="history">
<h4>Recent Combinations:</h4>
{history.map(({ keys, timestamp }, index) => (
<div key={index} className="history-entry">
<code>{keys}</code>
<span className="timestamp">
{new Date(timestamp).toLocaleTimeString()}
</span>
</div>
))}
</div>
<div className="status">
Recording: {isRecording ? '🔴 Active' : '⚫ Stopped'}
</div>
</div>
);
}function HotkeyConflictDetector({ existingHotkeys }) {
const [keys, { start, stop, resetKeys, isRecording }] = useRecordHotkeys();
const [conflicts, setConflicts] = useState([]);
useEffect(() => {
if (keys.size > 0) {
const combination = Array.from(keys).join('+');
const foundConflicts = existingHotkeys.filter(hotkey =>
hotkey.combination === combination
);
setConflicts(foundConflicts);
} else {
setConflicts([]);
}
}, [keys, existingHotkeys]);
return (
<div className="conflict-detector">
<h3>Hotkey Conflict Detection</h3>
<div className="input-area">
<p>Press keys to check for conflicts:</p>
<div className="key-display">
{keys.size > 0 ? Array.from(keys).join(' + ') : 'No keys pressed'}
</div>
{conflicts.length > 0 && (
<div className="conflicts">
<strong>⚠️ Conflicts detected:</strong>
<ul>
{conflicts.map((conflict, index) => (
<li key={index}>
<code>{conflict.combination}</code> - {conflict.description}
</li>
))}
</ul>
</div>
)}
</div>
<div className="controls">
<button onClick={isRecording ? stop : start}>
{isRecording ? 'Stop' : 'Start'} Checking
</button>
<button onClick={resetKeys}>
Clear
</button>
</div>
</div>
);
}function HotkeyBuilder({ onBuild }) {
const [keys, { start, stop, resetKeys, isRecording }] = useRecordHotkeys();
const [description, setDescription] = useState('');
const [builtHotkeys, setBuiltHotkeys] = useState([]);
const buildHotkey = () => {
if (keys.size === 0) return;
const combination = Array.from(keys).join('+');
const hotkey = {
keys: combination,
description: description || 'Unnamed hotkey',
id: Date.now()
};
setBuiltHotkeys(prev => [...prev, hotkey]);
onBuild(hotkey);
resetKeys();
setDescription('');
};
return (
<div className="hotkey-builder">
<h3>Build Custom Hotkeys</h3>
<div className="builder-form">
<div className="key-input">
<label>Key Combination:</label>
<div className="key-display">
{keys.size > 0 ? Array.from(keys).join(' + ') : 'Press keys...'}
</div>
<button onClick={isRecording ? stop : start}>
{isRecording ? 'Stop Recording' : 'Record Keys'}
</button>
</div>
<div className="description-input">
<label>Description:</label>
<input
type="text"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="What does this hotkey do?"
/>
</div>
<div className="actions">
<button onClick={buildHotkey} disabled={keys.size === 0}>
Add Hotkey
</button>
<button onClick={resetKeys}>
Clear Keys
</button>
</div>
</div>
<div className="built-hotkeys">
<h4>Built Hotkeys:</h4>
{builtHotkeys.map(hotkey => (
<div key={hotkey.id} className="hotkey-item">
<code>{hotkey.keys}</code> - {hotkey.description}
</div>
))}
</div>
</div>
);
}The recording system automatically:
The hook handles cross-browser differences in key event handling and provides consistent key naming across different platforms and keyboard layouts.
The recording system automatically cleans up event listeners when:
Install with Tessl CLI
npx tessl i tessl/npm-react-hotkeys-hook