0
# Key Recording
1
2
Hook for recording keyboard input to discover key combinations, useful for hotkey configuration interfaces and debugging.
3
4
## Capabilities
5
6
### useRecordHotkeys Hook
7
8
Hook for recording keyboard input to capture key combinations as they are pressed.
9
10
```typescript { .api }
11
/**
12
* Hook for recording keyboard input to discover key combinations
13
* @param useKey - Whether to record key names instead of key codes (default: false)
14
* @returns Tuple with recorded keys set and control functions
15
*/
16
function useRecordHotkeys(useKey?: boolean): [
17
Set<string>,
18
{
19
start: () => void;
20
stop: () => void;
21
resetKeys: () => void;
22
isRecording: boolean;
23
}
24
];
25
```
26
27
**Usage Examples:**
28
29
```typescript
30
import { useRecordHotkeys } from 'react-hotkeys-hook';
31
32
function HotkeyRecorder() {
33
const [keys, { start, stop, resetKeys, isRecording }] = useRecordHotkeys();
34
35
return (
36
<div>
37
<div>
38
Recorded keys: {Array.from(keys).join(' + ')}
39
</div>
40
41
<button onClick={start} disabled={isRecording}>
42
Start Recording
43
</button>
44
<button onClick={stop} disabled={!isRecording}>
45
Stop Recording
46
</button>
47
<button onClick={resetKeys}>
48
Clear Keys
49
</button>
50
51
<div>
52
Status: {isRecording ? 'Recording...' : 'Stopped'}
53
</div>
54
</div>
55
);
56
}
57
```
58
59
### Recording Key Names vs Key Codes
60
61
Control whether to record key names or key codes using the `useKey` parameter.
62
63
```typescript
64
// Record key codes (default) - more reliable across keyboards
65
const [codes, codeControls] = useRecordHotkeys(false);
66
// Example output: ['ControlLeft', 'KeyK']
67
68
// Record key names - more readable but less reliable
69
const [names, nameControls] = useRecordHotkeys(true);
70
// Example output: ['Control', 'k']
71
```
72
73
## Practical Examples
74
75
### Hotkey Configuration Interface
76
77
```typescript
78
function HotkeyConfig({ onSave }) {
79
const [keys, { start, stop, resetKeys, isRecording }] = useRecordHotkeys();
80
const [savedHotkey, setSavedHotkey] = useState('');
81
82
const handleSave = () => {
83
const combination = Array.from(keys).join('+');
84
setSavedHotkey(combination);
85
onSave(combination);
86
resetKeys();
87
};
88
89
return (
90
<div className="hotkey-config">
91
<h3>Configure Hotkey</h3>
92
93
<div className="recording-area">
94
<p>Press keys to record combination:</p>
95
<div className="key-display">
96
{keys.size > 0 ? Array.from(keys).join(' + ') : 'No keys recorded'}
97
</div>
98
</div>
99
100
<div className="controls">
101
<button onClick={isRecording ? stop : start}>
102
{isRecording ? 'Stop Recording' : 'Start Recording'}
103
</button>
104
<button onClick={resetKeys} disabled={keys.size === 0}>
105
Clear
106
</button>
107
<button onClick={handleSave} disabled={keys.size === 0}>
108
Save Hotkey
109
</button>
110
</div>
111
112
{savedHotkey && (
113
<div className="saved-hotkey">
114
Saved hotkey: <code>{savedHotkey}</code>
115
</div>
116
)}
117
</div>
118
);
119
}
120
```
121
122
### Live Hotkey Debugger
123
124
```typescript
125
function HotkeyDebugger() {
126
const [keys, { start, stop, isRecording }] = useRecordHotkeys();
127
const [history, setHistory] = useState([]);
128
129
useEffect(() => {
130
// Auto-start recording for debugging
131
start();
132
return stop;
133
}, [start, stop]);
134
135
useEffect(() => {
136
if (keys.size > 0) {
137
const combination = Array.from(keys).join('+');
138
setHistory(prev => [
139
...prev.slice(-9), // Keep last 10 entries
140
{ keys: combination, timestamp: Date.now() }
141
]);
142
}
143
}, [keys]);
144
145
return (
146
<div className="hotkey-debugger">
147
<h3>Hotkey Debugger</h3>
148
149
<div className="current-keys">
150
<strong>Currently Pressed:</strong>
151
<code>{keys.size > 0 ? Array.from(keys).join(' + ') : 'None'}</code>
152
</div>
153
154
<div className="history">
155
<h4>Recent Combinations:</h4>
156
{history.map(({ keys, timestamp }, index) => (
157
<div key={index} className="history-entry">
158
<code>{keys}</code>
159
<span className="timestamp">
160
{new Date(timestamp).toLocaleTimeString()}
161
</span>
162
</div>
163
))}
164
</div>
165
166
<div className="status">
167
Recording: {isRecording ? '🔴 Active' : '⚫ Stopped'}
168
</div>
169
</div>
170
);
171
}
172
```
173
174
### Hotkey Conflict Detection
175
176
```typescript
177
function HotkeyConflictDetector({ existingHotkeys }) {
178
const [keys, { start, stop, resetKeys, isRecording }] = useRecordHotkeys();
179
const [conflicts, setConflicts] = useState([]);
180
181
useEffect(() => {
182
if (keys.size > 0) {
183
const combination = Array.from(keys).join('+');
184
const foundConflicts = existingHotkeys.filter(hotkey =>
185
hotkey.combination === combination
186
);
187
setConflicts(foundConflicts);
188
} else {
189
setConflicts([]);
190
}
191
}, [keys, existingHotkeys]);
192
193
return (
194
<div className="conflict-detector">
195
<h3>Hotkey Conflict Detection</h3>
196
197
<div className="input-area">
198
<p>Press keys to check for conflicts:</p>
199
<div className="key-display">
200
{keys.size > 0 ? Array.from(keys).join(' + ') : 'No keys pressed'}
201
</div>
202
203
{conflicts.length > 0 && (
204
<div className="conflicts">
205
<strong>⚠️ Conflicts detected:</strong>
206
<ul>
207
{conflicts.map((conflict, index) => (
208
<li key={index}>
209
<code>{conflict.combination}</code> - {conflict.description}
210
</li>
211
))}
212
</ul>
213
</div>
214
)}
215
</div>
216
217
<div className="controls">
218
<button onClick={isRecording ? stop : start}>
219
{isRecording ? 'Stop' : 'Start'} Checking
220
</button>
221
<button onClick={resetKeys}>
222
Clear
223
</button>
224
</div>
225
</div>
226
);
227
}
228
```
229
230
### Custom Hotkey Builder
231
232
```typescript
233
function HotkeyBuilder({ onBuild }) {
234
const [keys, { start, stop, resetKeys, isRecording }] = useRecordHotkeys();
235
const [description, setDescription] = useState('');
236
const [builtHotkeys, setBuiltHotkeys] = useState([]);
237
238
const buildHotkey = () => {
239
if (keys.size === 0) return;
240
241
const combination = Array.from(keys).join('+');
242
const hotkey = {
243
keys: combination,
244
description: description || 'Unnamed hotkey',
245
id: Date.now()
246
};
247
248
setBuiltHotkeys(prev => [...prev, hotkey]);
249
onBuild(hotkey);
250
251
resetKeys();
252
setDescription('');
253
};
254
255
return (
256
<div className="hotkey-builder">
257
<h3>Build Custom Hotkeys</h3>
258
259
<div className="builder-form">
260
<div className="key-input">
261
<label>Key Combination:</label>
262
<div className="key-display">
263
{keys.size > 0 ? Array.from(keys).join(' + ') : 'Press keys...'}
264
</div>
265
<button onClick={isRecording ? stop : start}>
266
{isRecording ? 'Stop Recording' : 'Record Keys'}
267
</button>
268
</div>
269
270
<div className="description-input">
271
<label>Description:</label>
272
<input
273
type="text"
274
value={description}
275
onChange={(e) => setDescription(e.target.value)}
276
placeholder="What does this hotkey do?"
277
/>
278
</div>
279
280
<div className="actions">
281
<button onClick={buildHotkey} disabled={keys.size === 0}>
282
Add Hotkey
283
</button>
284
<button onClick={resetKeys}>
285
Clear Keys
286
</button>
287
</div>
288
</div>
289
290
<div className="built-hotkeys">
291
<h4>Built Hotkeys:</h4>
292
{builtHotkeys.map(hotkey => (
293
<div key={hotkey.id} className="hotkey-item">
294
<code>{hotkey.keys}</code> - {hotkey.description}
295
</div>
296
))}
297
</div>
298
</div>
299
);
300
}
301
```
302
303
## Implementation Notes
304
305
### Event Handling
306
307
The recording system automatically:
308
- Prevents default browser behavior during recording
309
- Stops event propagation to avoid conflicts
310
- Handles synthetic events (ignores Chrome autofill events)
311
- Maps key codes to normalized key names
312
313
### Browser Compatibility
314
315
The hook handles cross-browser differences in key event handling and provides consistent key naming across different platforms and keyboard layouts.
316
317
### Memory Management
318
319
The recording system automatically cleans up event listeners when:
320
- The component unmounts
321
- Recording is stopped
322
- The hook is re-initialized