0
# User Input Handling
1
2
Comprehensive input handling system for creating interactive CLI applications with keyboard navigation, focus management, and stream access.
3
4
## Types
5
6
These context Props types are available for import and use with the corresponding hooks:
7
8
```typescript { .api }
9
/**
10
* Props type for AppContext - used with useApp hook
11
*/
12
interface AppProps {
13
/**
14
* Exit (unmount) the whole Ink app
15
* @param error - Optional error to exit with
16
*/
17
readonly exit: (error?: Error) => void;
18
}
19
20
/**
21
* Props type for StdinContext - used with useStdin hook
22
*/
23
interface StdinProps {
24
/**
25
* Stdin stream passed to render() or process.stdin by default
26
*/
27
readonly stdin: NodeJS.ReadStream;
28
29
/**
30
* Ink's version of setRawMode that handles Ctrl+C properly
31
* Use instead of process.stdin.setRawMode
32
*/
33
readonly setRawMode: (value: boolean) => void;
34
35
/**
36
* Whether the current stdin supports setRawMode
37
*/
38
readonly isRawModeSupported: boolean;
39
40
/**
41
* Internal flag for exit on Ctrl+C behavior
42
*/
43
readonly internal_exitOnCtrlC: boolean;
44
45
/**
46
* Internal event emitter for input events
47
*/
48
readonly internal_eventEmitter: EventEmitter;
49
}
50
51
/**
52
* Props type for StdoutContext - used with useStdout hook
53
*/
54
interface StdoutProps {
55
/**
56
* Stdout stream passed to render() or process.stdout by default
57
*/
58
readonly stdout: NodeJS.WriteStream;
59
60
/**
61
* Write string to stdout while preserving Ink's output
62
* Useful for displaying external information without conflicts
63
*/
64
readonly write: (data: string) => void;
65
}
66
67
/**
68
* Props type for StderrContext - used with useStderr hook
69
*/
70
interface StderrProps {
71
/**
72
* Stderr stream passed to render() or process.stderr by default
73
*/
74
readonly stderr: NodeJS.WriteStream;
75
76
/**
77
* Write string to stderr while preserving Ink's output
78
* Useful for displaying external information without conflicts
79
*/
80
readonly write: (data: string) => void;
81
}
82
```
83
84
**Usage Examples:**
85
86
```typescript
87
import React from "react";
88
import {
89
render,
90
Text,
91
useApp,
92
useStdin,
93
useStdout,
94
useStderr,
95
type AppProps,
96
type StdinProps,
97
type StdoutProps,
98
type StderrProps
99
} from "ink";
100
101
// Using Props types for custom contexts or components
102
function CustomAppProvider({ children }: { children: React.ReactNode }) {
103
const appContext: AppProps = {
104
exit: (error?: Error) => {
105
console.log("Custom exit handler");
106
process.exit(error ? 1 : 0);
107
}
108
};
109
110
return (
111
<AppContext.Provider value={appContext}>
112
{children}
113
</AppContext.Provider>
114
);
115
}
116
```
117
118
## Capabilities
119
120
### Input Handling
121
122
Hook for handling user keyboard input with detailed key information parsing.
123
124
```typescript { .api }
125
/**
126
* Handle user keyboard input with parsed key information
127
* @param inputHandler - Function called for each input event
128
* @param options - Input handling configuration
129
*/
130
function useInput(
131
inputHandler: (input: string, key: Key) => void,
132
options?: InputOptions
133
): void;
134
135
interface InputOptions {
136
/**
137
* Enable or disable input capturing
138
* @default true
139
*/
140
isActive?: boolean;
141
}
142
143
interface Key {
144
upArrow: boolean; // Up arrow key pressed
145
downArrow: boolean; // Down arrow key pressed
146
leftArrow: boolean; // Left arrow key pressed
147
rightArrow: boolean; // Right arrow key pressed
148
pageDown: boolean; // Page Down key pressed
149
pageUp: boolean; // Page Up key pressed
150
return: boolean; // Return (Enter) key pressed
151
escape: boolean; // Escape key pressed
152
ctrl: boolean; // Ctrl key pressed
153
shift: boolean; // Shift key pressed
154
tab: boolean; // Tab key pressed
155
backspace: boolean; // Backspace key pressed
156
delete: boolean; // Delete key pressed
157
meta: boolean; // Meta key pressed
158
}
159
```
160
161
**Usage Examples:**
162
163
```typescript
164
import React, { useState } from "react";
165
import { render, Box, Text, useInput } from "ink";
166
167
// Basic input handling
168
function InputDemo() {
169
const [input, setInput] = useState("");
170
const [lastKey, setLastKey] = useState("");
171
172
useInput((input, key) => {
173
if (key.return) {
174
setInput("");
175
return;
176
}
177
178
if (key.backspace) {
179
setInput(prev => prev.slice(0, -1));
180
return;
181
}
182
183
if (input) {
184
setInput(prev => prev + input);
185
}
186
187
// Track special keys
188
if (key.upArrow) setLastKey("Up Arrow");
189
if (key.downArrow) setLastKey("Down Arrow");
190
if (key.ctrl) setLastKey("Ctrl pressed");
191
if (key.escape) setLastKey("Escape");
192
});
193
194
return (
195
<Box flexDirection="column">
196
<Text>Input: {input}</Text>
197
<Text>Last key: {lastKey}</Text>
198
<Text dimColor>Type text, use arrows, press Enter to clear</Text>
199
</Box>
200
);
201
}
202
203
// Navigation with arrow keys
204
function Navigation() {
205
const [selectedIndex, setSelectedIndex] = useState(0);
206
const items = ["Option 1", "Option 2", "Option 3"];
207
208
useInput((input, key) => {
209
if (key.upArrow) {
210
setSelectedIndex(prev => Math.max(0, prev - 1));
211
}
212
213
if (key.downArrow) {
214
setSelectedIndex(prev => Math.min(items.length - 1, prev + 1));
215
}
216
});
217
218
return (
219
<Box flexDirection="column">
220
{items.map((item, index) => (
221
<Text key={index} color={index === selectedIndex ? "blue" : "white"}>
222
{index === selectedIndex ? "► " : " "}{item}
223
</Text>
224
))}
225
</Box>
226
);
227
}
228
229
// Conditional input handling
230
function ConditionalInput() {
231
const [isActive, setIsActive] = useState(true);
232
233
useInput((input, key) => {
234
if (input === "q") {
235
setIsActive(false);
236
}
237
}, { isActive });
238
239
return (
240
<Text color={isActive ? "green" : "red"}>
241
Input {isActive ? "active" : "inactive"}. Press 'q' to toggle.
242
</Text>
243
);
244
}
245
```
246
247
### Focus Management
248
249
Hook for making components focusable and managing focus navigation with Tab key.
250
251
```typescript { .api }
252
/**
253
* Make component focusable for Tab navigation
254
* @param options - Focus configuration
255
* @returns Focus state and control functions
256
*/
257
function useFocus(options?: FocusOptions): FocusResult;
258
259
interface FocusOptions {
260
/**
261
* Enable or disable focus for this component
262
* @default true
263
*/
264
isActive?: boolean;
265
266
/**
267
* Auto focus this component if no active component exists
268
* @default false
269
*/
270
autoFocus?: boolean;
271
272
/**
273
* Assign ID for programmatic focusing
274
*/
275
id?: string;
276
}
277
278
interface FocusResult {
279
/**
280
* Whether this component is currently focused
281
*/
282
isFocused: boolean;
283
284
/**
285
* Focus a specific component by ID
286
*/
287
focus: (id: string) => void;
288
}
289
```
290
291
**Usage Examples:**
292
293
```typescript
294
import React, { useState } from "react";
295
import { render, Box, Text, useInput, useFocus } from "ink";
296
297
// Focusable button component
298
function FocusableButton({ children, onSelect, id }: {
299
children: string;
300
onSelect: () => void;
301
id?: string;
302
}) {
303
const { isFocused } = useFocus({ id });
304
305
useInput((input, key) => {
306
if (key.return && isFocused) {
307
onSelect();
308
}
309
});
310
311
return (
312
<Box>
313
<Text color={isFocused ? "blue" : "white"} inverse={isFocused}>
314
{isFocused ? "► " : " "}{children}
315
</Text>
316
</Box>
317
);
318
}
319
320
// Menu with focusable items
321
function FocusableMenu() {
322
const [selected, setSelected] = useState("");
323
324
return (
325
<Box flexDirection="column">
326
<Text>Use Tab to navigate, Enter to select:</Text>
327
328
<FocusableButton
329
id="option1"
330
onSelect={() => setSelected("Option 1")}
331
>
332
Option 1
333
</FocusableButton>
334
335
<FocusableButton
336
id="option2"
337
onSelect={() => setSelected("Option 2")}
338
>
339
Option 2
340
</FocusableButton>
341
342
<FocusableButton
343
id="option3"
344
onSelect={() => setSelected("Option 3")}
345
>
346
Option 3
347
</FocusableButton>
348
349
{selected && (
350
<Text color="green">Selected: {selected}</Text>
351
)}
352
</Box>
353
);
354
}
355
356
// Auto-focus example
357
function AutoFocusInput() {
358
const { isFocused } = useFocus({ autoFocus: true });
359
const [value, setValue] = useState("");
360
361
useInput((input, key) => {
362
if (!isFocused) return;
363
364
if (key.backspace) {
365
setValue(prev => prev.slice(0, -1));
366
} else if (input) {
367
setValue(prev => prev + input);
368
}
369
});
370
371
return (
372
<Box>
373
<Text>Input: </Text>
374
<Text color="blue" inverse={isFocused}>{value || " "}</Text>
375
</Box>
376
);
377
}
378
```
379
380
### Focus Manager
381
382
Hook for programmatically controlling focus across all components.
383
384
```typescript { .api }
385
/**
386
* Control focus management for all components
387
* @returns Focus management functions
388
*/
389
function useFocusManager(): FocusManagerResult;
390
391
interface FocusManagerResult {
392
/**
393
* Enable focus management for all components
394
*/
395
enableFocus: () => void;
396
397
/**
398
* Disable focus management, active component loses focus
399
*/
400
disableFocus: () => void;
401
402
/**
403
* Switch focus to next focusable component
404
*/
405
focusNext: () => void;
406
407
/**
408
* Switch focus to previous focusable component
409
*/
410
focusPrevious: () => void;
411
412
/**
413
* Focus specific component by ID
414
*/
415
focus: (id: string) => void;
416
}
417
```
418
419
**Usage Examples:**
420
421
```typescript
422
import React from "react";
423
import { render, Box, Text, useInput, useFocus, useFocusManager } from "ink";
424
425
function FocusController() {
426
const { enableFocus, disableFocus, focusNext, focusPrevious, focus } = useFocusManager();
427
428
useInput((input, key) => {
429
if (input === "e") enableFocus();
430
if (input === "d") disableFocus();
431
if (input === "n") focusNext();
432
if (input === "p") focusPrevious();
433
if (input === "1") focus("first");
434
if (input === "2") focus("second");
435
});
436
437
return (
438
<Box flexDirection="column">
439
<Text>Focus Control:</Text>
440
<Text dimColor>e: enable, d: disable, n: next, p: previous</Text>
441
<Text dimColor>1: focus first, 2: focus second</Text>
442
443
<FocusableItem id="first">First Item</FocusableItem>
444
<FocusableItem id="second">Second Item</FocusableItem>
445
</Box>
446
);
447
}
448
449
function FocusableItem({ id, children }: { id: string; children: string }) {
450
const { isFocused } = useFocus({ id });
451
return <Text color={isFocused ? "blue" : "white"}>{children}</Text>;
452
}
453
```
454
455
### Application Control
456
457
Hook for controlling the application lifecycle.
458
459
```typescript { .api }
460
/**
461
* Access application control functions
462
* @returns Application control interface
463
*/
464
function useApp(): AppResult;
465
466
interface AppResult {
467
/**
468
* Exit (unmount) the whole Ink app
469
* @param error - Optional error to exit with
470
*/
471
exit: (error?: Error) => void;
472
}
473
```
474
475
**Usage Examples:**
476
477
```typescript
478
import React, { useEffect } from "react";
479
import { render, Text, useInput, useApp } from "ink";
480
481
// Exit on specific key
482
function ExitableApp() {
483
const { exit } = useApp();
484
485
useInput((input, key) => {
486
if (input === "q" || key.escape) {
487
exit();
488
}
489
});
490
491
return <Text>Press 'q' or Escape to exit</Text>;
492
}
493
494
// Exit with error
495
function ErrorApp() {
496
const { exit } = useApp();
497
498
useEffect(() => {
499
const timer = setTimeout(() => {
500
exit(new Error("Something went wrong"));
501
}, 3000);
502
503
return () => clearTimeout(timer);
504
}, [exit]);
505
506
return <Text color="red">App will exit with error in 3 seconds...</Text>;
507
}
508
```
509
510
### Stream Access
511
512
Hooks for accessing stdin, stdout, and stderr streams directly.
513
514
```typescript { .api }
515
/**
516
* Access stdin stream and related utilities
517
*/
518
function useStdin(): StdinResult;
519
520
/**
521
* Access stdout stream and write function
522
*/
523
function useStdout(): StdoutResult;
524
525
/**
526
* Access stderr stream and write function
527
*/
528
function useStderr(): StderrResult;
529
530
interface StdinResult {
531
/**
532
* Input stream
533
*/
534
stdin: NodeJS.ReadStream;
535
536
/**
537
* Set raw mode for stdin (use Ink's version, not process.stdin.setRawMode)
538
*/
539
setRawMode: (value: boolean) => void;
540
541
/**
542
* Whether stdin supports raw mode
543
*/
544
isRawModeSupported: boolean;
545
546
/**
547
* Internal flag for exit on Ctrl+C behavior
548
*/
549
internal_exitOnCtrlC: boolean;
550
551
/**
552
* Internal event emitter for input events
553
*/
554
internal_eventEmitter: EventEmitter;
555
}
556
557
interface StdoutResult {
558
/**
559
* Output stream where app renders
560
*/
561
stdout: NodeJS.WriteStream;
562
563
/**
564
* Write string to stdout while preserving Ink's output
565
* Useful for displaying external information without conflicts
566
*/
567
write: (data: string) => void;
568
}
569
570
interface StderrResult {
571
/**
572
* Error stream for error output
573
*/
574
stderr: NodeJS.WriteStream;
575
576
/**
577
* Write string to stderr while preserving Ink's output
578
* Useful for displaying external information without conflicts
579
*/
580
write: (data: string) => void;
581
}
582
```
583
584
**Usage Examples:**
585
586
```typescript
587
import React, { useEffect, useState } from "react";
588
import { render, Text, useStdin, useStdout } from "ink";
589
590
// Direct stream access
591
function StreamAccess() {
592
const { stdin, setRawMode, isRawModeSupported } = useStdin();
593
const stdout = useStdout();
594
const [rawMode, setRawModeState] = useState(false);
595
596
useEffect(() => {
597
if (isRawModeSupported) {
598
setRawMode(true);
599
setRawModeState(true);
600
601
return () => {
602
setRawMode(false);
603
setRawModeState(false);
604
};
605
}
606
}, [setRawMode, isRawModeSupported]);
607
608
return (
609
<Text>
610
Raw mode: {rawMode ? "enabled" : "disabled"}
611
(supported: {isRawModeSupported ? "yes" : "no"})
612
</Text>
613
);
614
}
615
```