or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.mdkey-checking.mdkey-recording.mdmain-hook.mdscope-management.md

key-recording.mddocs/

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