ink 6.7.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/build/ansi-tokenizer.d.ts +38 -0
  2. package/build/ansi-tokenizer.js +316 -0
  3. package/build/ansi-tokenizer.js.map +1 -0
  4. package/build/components/AnimationContext.d.ts +9 -0
  5. package/build/components/AnimationContext.js +13 -0
  6. package/build/components/AnimationContext.js.map +1 -0
  7. package/build/components/App.d.ts +5 -2
  8. package/build/components/App.js +192 -41
  9. package/build/components/App.js.map +1 -1
  10. package/build/components/AppContext.d.ts +33 -3
  11. package/build/components/AppContext.js +2 -1
  12. package/build/components/AppContext.js.map +1 -1
  13. package/build/components/Box.d.ts +16 -3
  14. package/build/components/ErrorBoundary.d.ts +2 -2
  15. package/build/components/ErrorOverview.js +6 -6
  16. package/build/components/ErrorOverview.js.map +1 -1
  17. package/build/components/Static.js.map +1 -1
  18. package/build/components/StdinContext.d.ts +7 -1
  19. package/build/components/StdinContext.js +1 -0
  20. package/build/components/StdinContext.js.map +1 -1
  21. package/build/components/Text.d.ts +1 -1
  22. package/build/components/Text.js +1 -1
  23. package/build/components/Text.js.map +1 -1
  24. package/build/components/Transform.d.ts +1 -1
  25. package/build/devtools-window-polyfill.js +7 -4
  26. package/build/devtools-window-polyfill.js.map +1 -1
  27. package/build/devtools.js +31 -6
  28. package/build/devtools.js.map +1 -1
  29. package/build/dom.d.ts +5 -1
  30. package/build/dom.js +25 -5
  31. package/build/dom.js.map +1 -1
  32. package/build/hooks/use-animation.d.ts +49 -0
  33. package/build/hooks/use-animation.js +87 -0
  34. package/build/hooks/use-animation.js.map +1 -0
  35. package/build/hooks/use-app.d.ts +5 -2
  36. package/build/hooks/use-app.js +1 -1
  37. package/build/hooks/use-box-metrics.d.ts +59 -0
  38. package/build/hooks/use-box-metrics.js +88 -0
  39. package/build/hooks/use-box-metrics.js.map +1 -0
  40. package/build/hooks/use-cursor.d.ts +1 -1
  41. package/build/hooks/use-cursor.js +1 -1
  42. package/build/hooks/use-focus-manager.d.ts +17 -2
  43. package/build/hooks/use-focus-manager.js +2 -1
  44. package/build/hooks/use-focus-manager.js.map +1 -1
  45. package/build/hooks/use-focus.d.ts +2 -1
  46. package/build/hooks/use-focus.js +5 -4
  47. package/build/hooks/use-focus.js.map +1 -1
  48. package/build/hooks/use-input.d.ts +2 -1
  49. package/build/hooks/use-input.js +82 -80
  50. package/build/hooks/use-input.js.map +1 -1
  51. package/build/hooks/use-is-screen-reader-enabled.d.ts +2 -1
  52. package/build/hooks/use-is-screen-reader-enabled.js +2 -1
  53. package/build/hooks/use-is-screen-reader-enabled.js.map +1 -1
  54. package/build/hooks/use-paste.d.ts +35 -0
  55. package/build/hooks/use-paste.js +62 -0
  56. package/build/hooks/use-paste.js.map +1 -0
  57. package/build/hooks/use-stderr.d.ts +1 -1
  58. package/build/hooks/use-stderr.js +1 -1
  59. package/build/hooks/use-stdin.d.ts +4 -2
  60. package/build/hooks/use-stdin.js +2 -1
  61. package/build/hooks/use-stdin.js.map +1 -1
  62. package/build/hooks/use-stdout.d.ts +1 -1
  63. package/build/hooks/use-stdout.js +1 -1
  64. package/build/hooks/use-window-size.d.ts +18 -0
  65. package/build/hooks/use-window-size.js +22 -0
  66. package/build/hooks/use-window-size.js.map +1 -0
  67. package/build/index.d.ts +10 -1
  68. package/build/index.js +5 -0
  69. package/build/index.js.map +1 -1
  70. package/build/ink.d.ts +55 -6
  71. package/build/ink.js +433 -162
  72. package/build/ink.js.map +1 -1
  73. package/build/input-parser.d.ts +10 -0
  74. package/build/input-parser.js +194 -0
  75. package/build/input-parser.js.map +1 -0
  76. package/build/log-update.d.ts +1 -0
  77. package/build/log-update.js +13 -1
  78. package/build/log-update.js.map +1 -1
  79. package/build/measure-element.d.ts +4 -0
  80. package/build/measure-element.js +4 -0
  81. package/build/measure-element.js.map +1 -1
  82. package/build/output.d.ts +1 -0
  83. package/build/output.js +63 -5
  84. package/build/output.js.map +1 -1
  85. package/build/parse-keypress.d.ts +1 -3
  86. package/build/parse-keypress.js +19 -17
  87. package/build/parse-keypress.js.map +1 -1
  88. package/build/reconciler.js +48 -19
  89. package/build/reconciler.js.map +1 -1
  90. package/build/render-border.js +29 -18
  91. package/build/render-border.js.map +1 -1
  92. package/build/render-to-string.d.ts +38 -0
  93. package/build/render-to-string.js +116 -0
  94. package/build/render-to-string.js.map +1 -0
  95. package/build/render.d.ts +69 -3
  96. package/build/render.js +18 -11
  97. package/build/render.js.map +1 -1
  98. package/build/sanitize-ansi.d.ts +2 -0
  99. package/build/sanitize-ansi.js +27 -0
  100. package/build/sanitize-ansi.js.map +1 -0
  101. package/build/squash-text-nodes.js +2 -1
  102. package/build/squash-text-nodes.js.map +1 -1
  103. package/build/styles.d.ts +78 -16
  104. package/build/styles.js +102 -31
  105. package/build/styles.js.map +1 -1
  106. package/build/utils.d.ts +9 -0
  107. package/build/utils.js +19 -0
  108. package/build/utils.js.map +1 -0
  109. package/build/wrap-text.js +7 -0
  110. package/build/wrap-text.js.map +1 -1
  111. package/build/write-synchronized.d.ts +1 -1
  112. package/build/write-synchronized.js +4 -2
  113. package/build/write-synchronized.js.map +1 -1
  114. package/package.json +40 -101
  115. package/readme.md +674 -56
  116. package/build/apply-styles.js +0 -175
  117. package/build/build-layout.js +0 -77
  118. package/build/calculate-wrapped-text.js +0 -53
  119. package/build/components/Color.js +0 -62
  120. package/build/experimental/apply-style.js +0 -140
  121. package/build/experimental/dom.js +0 -123
  122. package/build/experimental/output.js +0 -91
  123. package/build/experimental/reconciler.js +0 -141
  124. package/build/experimental/renderer.js +0 -81
  125. package/build/hooks/useInput.js +0 -38
  126. package/build/instance.js +0 -205
  127. package/build/options.d.ts +0 -52
  128. package/build/options.js +0 -2
  129. package/build/options.js.map +0 -1
  130. package/build/screen-reader-update.d.ts +0 -13
  131. package/build/screen-reader-update.js +0 -38
  132. package/build/screen-reader-update.js.map +0 -1
package/readme.md CHANGED
@@ -24,6 +24,8 @@ Since Ink is a React renderer, all features of React are supported.
24
24
  Head over to the [React](https://reactjs.org) website for documentation on how to use it.
25
25
  Only Ink's methods are documented in this readme.
26
26
 
27
+ **Fully AI-generated pull requests are not accepted. You can use AI, but should be verified and cleaned up by a human. Only Opus 4.6+ (high-effort) and Codex 5.4+ (extra high) are accepted models. Preferably created with Opus and verified by Codex.**
28
+
27
29
  ---
28
30
 
29
31
  <div align="center">
@@ -42,6 +44,9 @@ Only Ink's methods are documented in this readme.
42
44
  npm install ink react
43
45
  ```
44
46
 
47
+ > [!NOTE]
48
+ > This readme documents the upcoming version of Ink. For the latest stable release, see [Ink on npm](https://www.npmjs.com/package/ink).
49
+
45
50
  ## Usage
46
51
 
47
52
  ```jsx
@@ -69,8 +74,6 @@ render(<Counter />);
69
74
 
70
75
  <img src="media/demo.svg" width="600">
71
76
 
72
- Feel free to play around with the code and fork this Repl at [https://repl.it/@vadimdemedes/ink-counter-demo](https://repl.it/@vadimdemedes/ink-counter-demo).
73
-
74
77
  ## Who's Using Ink?
75
78
 
76
79
  - [Claude Code](https://github.com/anthropics/claude-code) - An agentic coding tool made by Anthropic.
@@ -129,11 +132,12 @@ Feel free to play around with the code and fork this Repl at [https://repl.it/@v
129
132
  - [ElevenLabs CLI](https://github.com/elevenlabs/cli) - ElevenLabs agents client.
130
133
  - [SSH AI Chat](https://github.com/miantiao-me/ssh-ai-chat) - Chat with AI over SSH.
131
134
 
132
- *(PRs welcome. Append new entries at the end. Repos must have 100+ stars and showcase Ink beyond a basic list picker.)*
135
+ _(PRs welcome. Append new entries at the end. Repos must have 100+ stars and showcase Ink beyond a basic list picker.)_
133
136
 
134
137
  ## Contents
135
138
 
136
139
  - [Getting Started](#getting-started)
140
+ - [App Lifecycle](#app-lifecycle)
137
141
  - [Components](#components)
138
142
  - [`<Text>`](#text)
139
143
  - [`<Box>`](#box)
@@ -143,20 +147,26 @@ Feel free to play around with the code and fork this Repl at [https://repl.it/@v
143
147
  - [`<Transform>`](#transform)
144
148
  - [Hooks](#hooks)
145
149
  - [`useInput`](#useinputinputhandler-options)
150
+ - [`usePaste`](#usepastehandler-options)
146
151
  - [`useApp`](#useapp)
147
152
  - [`useStdin`](#usestdin)
148
153
  - [`useStdout`](#usestdout)
154
+ - [`useBoxMetrics`](#useboxmetricsref)
149
155
  - [`useStderr`](#usestderr)
156
+ - [`useWindowSize`](#usewindowsize)
150
157
  - [`useFocus`](#usefocusoptions)
151
158
  - [`useFocusManager`](#usefocusmanager)
152
159
  - [`useCursor`](#usecursor)
160
+ - [`useAnimation`](#useanimationoptions)
153
161
  - [API](#api)
154
162
  - [Testing](#testing)
155
163
  - [Using React Devtools](#using-react-devtools)
156
164
  - [Screen Reader Support](#screen-reader-support)
157
165
  - [Useful Components](#useful-components)
158
166
  - [Useful Hooks](#useful-hooks)
167
+ - [Recipes](#recipes)
159
168
  - [Examples](#examples)
169
+ - [Continuous Integration](#continuous-integration)
160
170
 
161
171
  ## Getting Started
162
172
 
@@ -223,6 +233,22 @@ Think of it as if every `<div>` in the browser had `display: flex`.
223
233
  See [`<Box>`](#box) built-in component below for documentation on how to use Flexbox layouts in Ink.
224
234
  Note that all text must be wrapped in a [`<Text>`](#text) component.
225
235
 
236
+ ## App Lifecycle
237
+
238
+ An Ink app is a Node.js process, so it stays alive only while there is active work in the event loop (timers, pending promises, [`useInput`](#useinputinputhandler-options) listening on `stdin`, etc.). If your component tree has no async work, the app will render once and exit immediately.
239
+
240
+ To exit the app, press **Ctrl+C** (enabled by default via [`exitOnCtrlC`](#exitonctrlc)), call [`exit()`](#exiterrororresult) from [`useApp`](#useapp) inside a component, or call [`unmount()`](#unmount) on the object returned by [`render()`](#rendertree-options).
241
+
242
+ Use [`waitUntilExit()`](#waituntilexit) to run code after the app is unmounted:
243
+
244
+ ```jsx
245
+ const {waitUntilExit} = render(<MyApp />);
246
+
247
+ await waitUntilExit();
248
+
249
+ console.log('App exited');
250
+ ```
251
+
226
252
  ## Components
227
253
 
228
254
  ### `<Text>`
@@ -250,7 +276,8 @@ const Example = () => (
250
276
  render(<Example />);
251
277
  ```
252
278
 
253
- **Note:** `<Text>` allows only text nodes and nested `<Text>` components inside of it. For example, `<Box>` component can't be used inside `<Text>`.
279
+ > [!NOTE]
280
+ > `<Text>` allows only text nodes and nested `<Text>` components inside of it. For example, `<Box>` component can't be used inside `<Text>`.
254
281
 
255
282
  #### color
256
283
 
@@ -342,11 +369,12 @@ Invert background and foreground colors.
342
369
  #### wrap
343
370
 
344
371
  Type: `string`\
345
- Allowed values: `wrap` `truncate` `truncate-start` `truncate-middle` `truncate-end`\
372
+ Allowed values: `wrap` `hard` `truncate` `truncate-start` `truncate-middle` `truncate-end`\
346
373
  Default: `wrap`
347
374
 
348
375
  This property tells Ink to wrap or truncate text if its width is larger than the container.
349
376
  If `wrap` is passed (the default), Ink will wrap text and split it into multiple lines.
377
+ If `hard` is passed, Ink will fill each line to the full column width, breaking words as necessary.
350
378
  If `truncate-*` is passed, Ink will truncate text instead, resulting in one line of text with the rest cut off.
351
379
 
352
380
  ```jsx
@@ -355,6 +383,11 @@ If `truncate-*` is passed, Ink will truncate text instead, resulting in one line
355
383
  </Box>
356
384
  //=> 'Hello\nWorld'
357
385
 
386
+ <Box width={7}>
387
+ <Text wrap="hard">Hello World</Text>
388
+ </Box>
389
+ //=> 'Hello W\norld'
390
+
358
391
  // `truncate` is an alias to `truncate-end`
359
392
  <Box width={7}>
360
393
  <Text wrap="truncate">Hello World</Text>
@@ -448,11 +481,33 @@ Percentages aren't supported yet; see https://github.com/facebook/yoga/issues/87
448
481
 
449
482
  ##### minHeight
450
483
 
484
+ Type: `number` `string`
485
+
486
+ Sets a minimum height of the element in lines (rows).
487
+ You can also set it as a percentage, which will calculate the minimum height based on the height of the parent element.
488
+
489
+ ##### maxWidth
490
+
451
491
  Type: `number`
452
492
 
453
- Sets a minimum height of the element.
493
+ Sets a maximum width of the element.
454
494
  Percentages aren't supported yet; see https://github.com/facebook/yoga/issues/872.
455
495
 
496
+ ##### maxHeight
497
+
498
+ Type: `number` `string`
499
+
500
+ Sets a maximum height of the element in lines (rows).
501
+ You can also set it as a percentage, which will calculate the maximum height based on the height of the parent element.
502
+
503
+ ##### aspectRatio
504
+
505
+ Type: `number`
506
+
507
+ Defines the aspect ratio (width/height) for the element.
508
+
509
+ Use it with at least one size constraint (`width`, `height`, `minHeight`, or `maxHeight`) so Ink can derive the missing dimension.
510
+
456
511
  #### Padding
457
512
 
458
513
  ##### paddingTop
@@ -757,7 +812,7 @@ See [flex-wrap](https://css-tricks.com/almanac/properties/f/flex-wrap/).
757
812
  ##### alignItems
758
813
 
759
814
  Type: `string`\
760
- Allowed values: `flex-start` `center` `flex-end`
815
+ Allowed values: `flex-start` `center` `flex-end` `stretch` `baseline`
761
816
 
762
817
  See [align-items](https://css-tricks.com/almanac/properties/a/align-items/).
763
818
 
@@ -815,7 +870,7 @@ See [align-items](https://css-tricks.com/almanac/properties/a/align-items/).
815
870
 
816
871
  Type: `string`\
817
872
  Default: `auto`\
818
- Allowed values: `auto` `flex-start` `center` `flex-end`
873
+ Allowed values: `auto` `flex-start` `center` `flex-end` `stretch` `baseline`
819
874
 
820
875
  See [align-self](https://css-tricks.com/almanac/properties/a/align-self/).
821
876
 
@@ -848,6 +903,16 @@ See [align-self](https://css-tricks.com/almanac/properties/a/align-self/).
848
903
  // X
849
904
  ```
850
905
 
906
+ ##### alignContent
907
+
908
+ Type: `string`\
909
+ Default: `flex-start`\
910
+ Allowed values: `flex-start` `flex-end` `center` `stretch` `space-between` `space-around` `space-evenly`
911
+
912
+ Defines alignment between flex lines on the cross axis when `flexWrap` creates multiple lines.
913
+ See [align-content](https://css-tricks.com/almanac/properties/a/align-content/).
914
+ Unlike CSS (`stretch`), Ink defaults to `flex-start` so wrapped lines stay compact and fixed-height boxes don't gain unexpected empty rows unless you opt in to stretching.
915
+
851
916
  ##### justifyContent
852
917
 
853
918
  Type: `string`\
@@ -890,6 +955,46 @@ See [justify-content](https://css-tricks.com/almanac/properties/j/justify-conten
890
955
  // [ X Y ]
891
956
  ```
892
957
 
958
+ #### Position
959
+
960
+ ##### position
961
+
962
+ Type: `string`\
963
+ Allowed values: `relative` `absolute` `static`\
964
+ Default: `relative`
965
+
966
+ Controls how the element is positioned.
967
+
968
+ When `position` is `static`, `top`, `right`, `bottom`, and `left` are ignored.
969
+
970
+ ##### top
971
+
972
+ Type: `number` `string`
973
+
974
+ Top offset for positioned elements.
975
+ You can also set it as a percentage of the parent size.
976
+
977
+ ##### right
978
+
979
+ Type: `number` `string`
980
+
981
+ Right offset for positioned elements.
982
+ You can also set it as a percentage of the parent size.
983
+
984
+ ##### bottom
985
+
986
+ Type: `number` `string`
987
+
988
+ Bottom offset for positioned elements.
989
+ You can also set it as a percentage of the parent size.
990
+
991
+ ##### left
992
+
993
+ Type: `number` `string`
994
+
995
+ Left offset for positioned elements.
996
+ You can also set it as a percentage of the parent size.
997
+
893
998
  #### Visibility
894
999
 
895
1000
  ##### display
@@ -985,7 +1090,7 @@ Alternatively, pass a custom border style like so:
985
1090
  bottomLeft: '↗',
986
1091
  bottom: '↑',
987
1092
  bottomRight: '↖',
988
- right: '←'
1093
+ right: '←',
989
1094
  }}
990
1095
  >
991
1096
  <Text>Custom</Text>
@@ -1127,6 +1232,76 @@ Dim the right border color.
1127
1232
  </Box>
1128
1233
  ```
1129
1234
 
1235
+ ##### borderBackgroundColor
1236
+
1237
+ Type: `string`
1238
+
1239
+ Change border background color.
1240
+ Accepts the same values as [`backgroundColor`](#backgroundcolor) in `<Text>` component.
1241
+ A shorthand for setting `borderTopBackgroundColor`, `borderRightBackgroundColor`, `borderBottomBackgroundColor`, and `borderLeftBackgroundColor`.
1242
+
1243
+ ```jsx
1244
+ <Box borderStyle="round" borderColor="white" borderBackgroundColor="green">
1245
+ <Text>Hello world</Text>
1246
+ </Box>
1247
+ ```
1248
+
1249
+ ##### borderTopBackgroundColor
1250
+
1251
+ Type: `string`
1252
+
1253
+ Change top border background color.
1254
+ Accepts the same values as [`backgroundColor`](#backgroundcolor) in `<Text>` component.
1255
+ Falls back to `borderBackgroundColor` if not specified.
1256
+
1257
+ ```jsx
1258
+ <Box borderStyle="round" borderColor="white" borderTopBackgroundColor="green">
1259
+ <Text>Hello world</Text>
1260
+ </Box>
1261
+ ```
1262
+
1263
+ ##### borderBottomBackgroundColor
1264
+
1265
+ Type: `string`
1266
+
1267
+ Change bottom border background color.
1268
+ Accepts the same values as [`backgroundColor`](#backgroundcolor) in `<Text>` component.
1269
+ Falls back to `borderBackgroundColor` if not specified.
1270
+
1271
+ ```jsx
1272
+ <Box borderStyle="round" borderColor="white" borderBottomBackgroundColor="green">
1273
+ <Text>Hello world</Text>
1274
+ </Box>
1275
+ ```
1276
+
1277
+ ##### borderRightBackgroundColor
1278
+
1279
+ Type: `string`
1280
+
1281
+ Change right border background color.
1282
+ Accepts the same values as [`backgroundColor`](#backgroundcolor) in `<Text>` component.
1283
+ Falls back to `borderBackgroundColor` if not specified.
1284
+
1285
+ ```jsx
1286
+ <Box borderStyle="round" borderColor="white" borderRightBackgroundColor="green">
1287
+ <Text>Hello world</Text>
1288
+ </Box>
1289
+ ```
1290
+
1291
+ ##### borderLeftBackgroundColor
1292
+
1293
+ Type: `string`
1294
+
1295
+ Change left border background color.
1296
+ Accepts the same values as [`backgroundColor`](#backgroundcolor) in `<Text>` component.
1297
+ Falls back to `borderBackgroundColor` if not specified.
1298
+
1299
+ ```jsx
1300
+ <Box borderStyle="round" borderColor="white" borderLeftBackgroundColor="green">
1301
+ <Text>Hello world</Text>
1302
+ </Box>
1303
+ ```
1304
+
1130
1305
  ##### borderTop
1131
1306
 
1132
1307
  Type: `boolean`\
@@ -1171,11 +1346,23 @@ Accepts the same values as [`color`](#color) in the `<Text>` component.
1171
1346
  <Text>Red background</Text>
1172
1347
  </Box>
1173
1348
 
1174
- <Box backgroundColor="#FF8800" width={20} height={3} marginTop={1} alignSelf="flex-start">
1349
+ <Box
1350
+ backgroundColor="#FF8800"
1351
+ width={20}
1352
+ height={3}
1353
+ marginTop={1}
1354
+ alignSelf="flex-start"
1355
+ >
1175
1356
  <Text>Orange background</Text>
1176
1357
  </Box>
1177
1358
 
1178
- <Box backgroundColor="rgb(0, 255, 0)" width={20} height={3} marginTop={1} alignSelf="flex-start">
1359
+ <Box
1360
+ backgroundColor="rgb(0, 255, 0)"
1361
+ width={20}
1362
+ height={3}
1363
+ marginTop={1}
1364
+ alignSelf="flex-start"
1365
+ >
1179
1366
  <Text>Green background</Text>
1180
1367
  </Box>
1181
1368
  </Box>
@@ -1194,7 +1381,12 @@ The background color fills the entire `<Box>` area and is inherited by child `<T
1194
1381
  Background colors work with borders and padding:
1195
1382
 
1196
1383
  ```jsx
1197
- <Box backgroundColor="cyan" borderStyle="round" padding={1} alignSelf="flex-start">
1384
+ <Box
1385
+ backgroundColor="cyan"
1386
+ borderStyle="round"
1387
+ padding={1}
1388
+ alignSelf="flex-start"
1389
+ >
1198
1390
  <Text>Background with border and padding</Text>
1199
1391
  </Box>
1200
1392
  ```
@@ -1303,8 +1495,8 @@ const Example = () => {
1303
1495
  ...previousTests,
1304
1496
  {
1305
1497
  id: previousTests.length,
1306
- title: `Test #${previousTests.length + 1}`
1307
- }
1498
+ title: `Test #${previousTests.length + 1}`,
1499
+ },
1308
1500
  ]);
1309
1501
 
1310
1502
  timer = setTimeout(run, 100);
@@ -1340,9 +1532,8 @@ const Example = () => {
1340
1532
  render(<Example />);
1341
1533
  ```
1342
1534
 
1343
- **Note:** `<Static>` only renders new items in the `items` prop and ignores items
1344
- that were previously rendered. This means that when you add new items to the `items`
1345
- array, changes you make to previous items will not trigger a rerender.
1535
+ > [!NOTE]
1536
+ > `<Static>` only renders new items in the `items` prop and ignores items that were previously rendered. This means that when you add new items to the `items` array, changes you make to previous items will not trigger a rerender.
1346
1537
 
1347
1538
  See [examples/static](examples/static/static.tsx) for an example usage of `<Static>` component.
1348
1539
 
@@ -1397,7 +1588,11 @@ For example, you might want to apply a [gradient to text](https://github.com/sin
1397
1588
  These use cases can't accept React nodes as input; they expect a string.
1398
1589
  That's what the `<Transform>` component does: it gives you an output string of its child components and lets you transform it in any way.
1399
1590
 
1400
- **Note:** `<Transform>` must be applied only to `<Text>` children components and shouldn't change the dimensions of the output; otherwise, the layout will be incorrect.
1591
+ > [!NOTE]
1592
+ > `<Transform>` must be applied only to `<Text>` children components and shouldn't change the dimensions of the output; otherwise, the layout will be incorrect.
1593
+
1594
+ > [!IMPORTANT]
1595
+ > When children use `<Text>` styling props (e.g. `color`, `bold`), the string passed to `transform` will contain [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code). If your transform manipulates whitespace or does string operations like `.trim()`, you may need to use ANSI-aware methods (e.g. from [`slice-ansi`](https://github.com/chalk/slice-ansi) or [`strip-ansi`](https://github.com/chalk/strip-ansi)).
1401
1596
 
1402
1597
  ```jsx
1403
1598
  import {render, Transform} from 'ink';
@@ -1420,12 +1615,11 @@ For example, to implement a hanging indent component, you can indent all the lin
1420
1615
  ```jsx
1421
1616
  import {render, Transform} from 'ink';
1422
1617
 
1423
- const HangingIndent = ({content, indent = 4, children, ...props}) => (
1618
+ const HangingIndent = ({indent = 4, children}) => (
1424
1619
  <Transform
1425
1620
  transform={(line, index) =>
1426
1621
  index === 0 ? line : ' '.repeat(indent) + line
1427
1622
  }
1428
- {...props}
1429
1623
  >
1430
1624
  {children}
1431
1625
  </Transform>
@@ -1439,12 +1633,7 @@ const text =
1439
1633
  'of my hands only. I lived there two years and two months. At ' +
1440
1634
  'present I am a sojourner in civilized life again.';
1441
1635
 
1442
- // Other text properties are allowed as well
1443
- render(
1444
- <HangingIndent bold dimColor indent={4}>
1445
- {text}
1446
- </HangingIndent>
1447
- );
1636
+ render(<HangingIndent indent={4}>{text}</HangingIndent>);
1448
1637
  ```
1449
1638
 
1450
1639
  #### transform(outputLine, index)
@@ -1470,7 +1659,7 @@ The zero-indexed line number of the line that's currently being transformed.
1470
1659
 
1471
1660
  ### useInput(inputHandler, options?)
1472
1661
 
1473
- This hook is used for handling user input.
1662
+ A React hook that returns `void` and handles user input.
1474
1663
  It's a more convenient alternative to using `useStdin` and listening for `data` events.
1475
1664
  The callback you pass to `useInput` is called for each character when the user enters any input.
1476
1665
  However, if the user pastes text and it's more than one character, the callback will be called only once, and the whole string will be passed as `input`.
@@ -1585,6 +1774,16 @@ Default: `false`
1585
1774
  If the Page Up or Page Down key was pressed, the corresponding property will be `true`.
1586
1775
  For example, if the user presses Page Down, `key.pageDown` equals `true`.
1587
1776
 
1777
+ ###### key.home
1778
+
1779
+ ###### key.end
1780
+
1781
+ Type: `boolean`\
1782
+ Default: `false`
1783
+
1784
+ If the Home or End key was pressed, the corresponding property will be `true`.
1785
+ For example, if the user presses End, `key.end` equals `true`.
1786
+
1588
1787
  ###### key.meta
1589
1788
 
1590
1789
  Type: `boolean`\
@@ -1639,23 +1838,77 @@ Default: `true`
1639
1838
  Enable or disable capturing of user input.
1640
1839
  Useful when there are multiple `useInput` hooks used at once to avoid handling the same input several times.
1641
1840
 
1841
+ ### usePaste(handler, options?)
1842
+
1843
+ A React hook that calls `handler` whenever the user pastes text. Bracketed paste mode (`\x1b[?2004h`) is automatically enabled while the hook is active, so pasted text arrives as a single string rather than being misinterpreted as individual key presses.
1844
+
1845
+ `usePaste` and `useInput` can be used together in the same component. They operate on separate event channels, so paste content is never forwarded to `useInput` handlers when `usePaste` is active.
1846
+
1847
+ ```jsx
1848
+ import {useInput, usePaste} from 'ink';
1849
+
1850
+ const MyInput = () => {
1851
+ useInput((input, key) => {
1852
+ // Only receives typed characters and key events, not pasted text.
1853
+ if (key.return) {
1854
+ // Submit
1855
+ }
1856
+ });
1857
+
1858
+ usePaste((text) => {
1859
+ // Receives the full pasted string, including newlines.
1860
+ console.log('Pasted:', text);
1861
+ });
1862
+
1863
+ return …
1864
+ };
1865
+ ```
1866
+
1867
+ #### handler(text)
1868
+
1869
+ Type: `Function`
1870
+
1871
+ Called with the full pasted string whenever the user pastes text. The string is delivered verbatim — newlines, escape sequences, and other special characters are preserved exactly as pasted.
1872
+
1873
+ ##### text
1874
+
1875
+ Type: `string`
1876
+
1877
+ The pasted text.
1878
+
1879
+ #### options
1880
+
1881
+ Type: `object`
1882
+
1883
+ ##### isActive
1884
+
1885
+ Type: `boolean`\
1886
+ Default: `true`
1887
+
1888
+ Enable or disable the paste handler. Useful when multiple components use `usePaste` and only one should be active at a time.
1889
+
1642
1890
  ### useApp()
1643
1891
 
1644
- `useApp` is a React hook that exposes a method to manually exit the app (unmount).
1892
+ A React hook that returns app lifecycle methods.
1645
1893
 
1646
- #### exit(error?)
1894
+ #### exit(errorOrResult?)
1647
1895
 
1648
1896
  Type: `Function`
1649
1897
 
1650
1898
  Exit (unmount) the whole Ink app.
1651
1899
 
1652
- ##### error
1900
+ ##### errorOrResult
1901
+
1902
+ Type: `Error | unknown`
1653
1903
 
1654
- Type: `Error`
1904
+ Optional value that controls how [`waitUntilExit`](#waituntilexit) settles:
1655
1905
 
1656
- Optional error. If passed, [`waitUntilExit`](waituntilexit) will reject with that error.
1906
+ - `exit()` resolves with `undefined`.
1907
+ - `exit(error)` rejects when `error` is an `Error`.
1908
+ - `exit(value)` resolves with `value`.
1657
1909
 
1658
1910
  ```js
1911
+ import {useEffect} from 'react';
1659
1912
  import {useApp} from 'ink';
1660
1913
 
1661
1914
  const Example = () => {
@@ -1666,15 +1919,39 @@ const Example = () => {
1666
1919
  setTimeout(() => {
1667
1920
  exit();
1668
1921
  }, 5000);
1669
- }, []);
1922
+ }, [exit]);
1670
1923
 
1671
1924
  return …
1672
1925
  };
1673
1926
  ```
1674
1927
 
1928
+ #### waitUntilRenderFlush()
1929
+
1930
+ Type: `Function`
1931
+
1932
+ Returns a promise that settles after pending render output is flushed to stdout.
1933
+
1934
+ ```js
1935
+ import {useEffect} from 'react';
1936
+ import {useApp} from 'ink';
1937
+
1938
+ const Example = () => {
1939
+ const {waitUntilRenderFlush} = useApp();
1940
+
1941
+ useEffect(() => {
1942
+ void (async () => {
1943
+ await waitUntilRenderFlush();
1944
+ runNextCommand();
1945
+ })();
1946
+ }, [waitUntilRenderFlush]);
1947
+
1948
+ return …;
1949
+ };
1950
+ ```
1951
+
1675
1952
  ### useStdin()
1676
1953
 
1677
- `useStdin` is a React hook that exposes the stdin stream.
1954
+ A React hook that returns the stdin stream and stdin-related utilities.
1678
1955
 
1679
1956
  #### stdin
1680
1957
 
@@ -1748,7 +2025,7 @@ const Example = () => {
1748
2025
 
1749
2026
  ### useStdout()
1750
2027
 
1751
- `useStdout` is a React hook that exposes the stdout stream where Ink renders your app.
2028
+ A React hook that returns the stdout stream where Ink renders your app and stdout-related utilities.
1752
2029
 
1753
2030
  #### stdout
1754
2031
 
@@ -1794,9 +2071,73 @@ const Example = () => {
1794
2071
 
1795
2072
  See additional usage example in [examples/use-stdout](examples/use-stdout/use-stdout.tsx).
1796
2073
 
2074
+ ### useBoxMetrics(ref)
2075
+
2076
+ A React hook that returns the current layout metrics for a tracked box element.
2077
+ It updates when layout changes (for example terminal resize, sibling/content changes, or position changes).
2078
+
2079
+ Use `hasMeasured` to detect when the currently tracked element has been measured.
2080
+
2081
+ #### ref
2082
+
2083
+ Type: `React.RefObject<DOMElement>`
2084
+
2085
+ A ref to the `<Box>` element to track.
2086
+
2087
+ ```jsx
2088
+ import {useRef} from 'react';
2089
+ import {Box, Text, useBoxMetrics} from 'ink';
2090
+
2091
+ const Example = () => {
2092
+ const ref = useRef(null);
2093
+ const {width, height, left, top, hasMeasured} = useBoxMetrics(ref);
2094
+
2095
+ return (
2096
+ <Box ref={ref}>
2097
+ <Text>
2098
+ {hasMeasured ? `${width}x${height} at ${left},${top}` : 'Measuring...'}
2099
+ </Text>
2100
+ </Box>
2101
+ );
2102
+ };
2103
+ ```
2104
+
2105
+ #### width
2106
+
2107
+ Type: `number`
2108
+
2109
+ Element width.
2110
+
2111
+ #### height
2112
+
2113
+ Type: `number`
2114
+
2115
+ Element height.
2116
+
2117
+ #### left
2118
+
2119
+ Type: `number`
2120
+
2121
+ Distance from the left edge of the parent.
2122
+
2123
+ #### top
2124
+
2125
+ Type: `number`
2126
+
2127
+ Distance from the top edge of the parent.
2128
+
2129
+ #### hasMeasured
2130
+
2131
+ Type: `boolean`
2132
+
2133
+ Whether the currently tracked element has been measured.
2134
+
2135
+ > [!NOTE]
2136
+ > The hook returns `{width: 0, height: 0, left: 0, top: 0}` until the first layout pass completes. It also returns zeros when the tracked ref is detached.
2137
+
1797
2138
  ### useStderr()
1798
2139
 
1799
- `useStderr` is a React hook that exposes the stderr stream.
2140
+ A React hook that returns the stderr stream and stderr-related utilities.
1800
2141
 
1801
2142
  #### stderr
1802
2143
 
@@ -1843,8 +2184,42 @@ const Example = () => {
1843
2184
  };
1844
2185
  ```
1845
2186
 
2187
+ ### useWindowSize()
2188
+
2189
+ A React hook that returns the current terminal dimensions and re-renders the component whenever the terminal is resized.
2190
+
2191
+ ```js
2192
+ import {Text, useWindowSize} from 'ink';
2193
+
2194
+ const Example = () => {
2195
+ const {columns, rows} = useWindowSize();
2196
+
2197
+ return (
2198
+ <Text>
2199
+ {columns}x{rows}
2200
+ </Text>
2201
+ );
2202
+ };
2203
+ ```
2204
+
2205
+ #### columns
2206
+
2207
+ Type: `number`
2208
+
2209
+ Number of columns (horizontal character cells).
2210
+
2211
+ #### rows
2212
+
2213
+ Type: `number`
2214
+
2215
+ Number of rows (vertical character cells).
2216
+
2217
+ > [!NOTE]
2218
+ > When the terminal is resized narrower, ghost lines may briefly appear depending on the terminal emulator's reflow behavior.
2219
+
1846
2220
  ### useFocus(options?)
1847
2221
 
2222
+ A React hook that returns focus state and focus controls for the current component.
1848
2223
  A component that uses the `useFocus` hook becomes "focusable" to Ink, so when the user presses <kbd>Tab</kbd>, Ink will switch focus to this component.
1849
2224
  If there are multiple components that execute the `useFocus` hook, focus will be given to them in the order in which these components are rendered.
1850
2225
  This hook returns an object with an `isFocused` boolean property, which determines whether this component is focused.
@@ -1889,13 +2264,14 @@ See example in [examples/use-focus](examples/use-focus/use-focus.tsx) and [examp
1889
2264
 
1890
2265
  ### useFocusManager()
1891
2266
 
1892
- This hook exposes methods to enable or disable focus management for all components or manually switch focus to the next or previous components.
2267
+ A React hook that returns methods to manage focus across focusable components.
1893
2268
 
1894
2269
  #### enableFocus()
1895
2270
 
1896
2271
  Enable focus management for all components.
1897
2272
 
1898
- **Note:** You don't need to call this method manually unless you've disabled focus management. Focus management is enabled by default.
2273
+ > [!NOTE]
2274
+ > You don't need to call this method manually unless you've disabled focus management. Focus management is enabled by default.
1899
2275
 
1900
2276
  ```js
1901
2277
  import {useFocusManager} from 'ink';
@@ -1936,7 +2312,8 @@ Switch focus to the next focusable component.
1936
2312
  If there's no active component right now, focus will be given to the first focusable component.
1937
2313
  If the active component is the last in the list of focusable components, focus will be switched to the first focusable component.
1938
2314
 
1939
- **Note:** Ink calls this method when user presses <kbd>Tab</kbd>.
2315
+ > [!NOTE]
2316
+ > Ink calls this method when user presses <kbd>Tab</kbd>.
1940
2317
 
1941
2318
  ```js
1942
2319
  import {useFocusManager} from 'ink';
@@ -1958,7 +2335,8 @@ Switch focus to the previous focusable component.
1958
2335
  If there's no active component right now, focus will be given to the first focusable component.
1959
2336
  If the active component is the first in the list of focusable components, focus will be switched to the last focusable component.
1960
2337
 
1961
- **Note:** Ink calls this method when user presses <kbd>Shift</kbd>+<kbd>Tab</kbd>.
2338
+ > [!NOTE]
2339
+ > Ink calls this method when user presses <kbd>Shift</kbd>+<kbd>Tab</kbd>.
1962
2340
 
1963
2341
  ```js
1964
2342
  import {useFocusManager} from 'ink';
@@ -1981,7 +2359,7 @@ const Example = () => {
1981
2359
  Type: `string`
1982
2360
 
1983
2361
  Switch focus to the component with the given [`id`](#id).
1984
- If there's no component with that ID, focus will be given to the next focusable component.
2362
+ If there's no component with that ID, focus is not changed.
1985
2363
 
1986
2364
  ```js
1987
2365
  import {useFocusManager, useInput} from 'ink';
@@ -2000,9 +2378,26 @@ const Example = () => {
2000
2378
  };
2001
2379
  ```
2002
2380
 
2381
+ #### activeId
2382
+
2383
+ Type: `string | undefined`
2384
+
2385
+ The ID of the currently focused component, or `undefined` if no component is focused.
2386
+
2387
+ ```js
2388
+ import {Text, useFocusManager} from 'ink';
2389
+
2390
+ const Example = () => {
2391
+ const {activeId} = useFocusManager();
2392
+
2393
+ return <Text>Focused: {activeId ?? 'none'}</Text>;
2394
+ };
2395
+ ```
2396
+
2003
2397
  ### useCursor()
2004
2398
 
2005
- `useCursor` lets you control the terminal cursor position after each render. This is essential for IME (Input Method Editor) support, where the composing character is displayed at the cursor location.
2399
+ A React hook that returns methods to control the terminal cursor position after each render.
2400
+ This is essential for IME (Input Method Editor) support, where the composing character is displayed at the cursor location.
2006
2401
 
2007
2402
  ```jsx
2008
2403
  import {useState} from 'react';
@@ -2019,7 +2414,10 @@ const TextInput = () => {
2019
2414
  return (
2020
2415
  <Box flexDirection="column">
2021
2416
  <Text>Type here:</Text>
2022
- <Text>{prompt}{text}</Text>
2417
+ <Text>
2418
+ {prompt}
2419
+ {text}
2420
+ </Text>
2023
2421
  </Box>
2024
2422
  );
2025
2423
  };
@@ -2051,7 +2449,8 @@ Row position from the top of the Ink output (0 = first line).
2051
2449
 
2052
2450
  ### useIsScreenReaderEnabled()
2053
2451
 
2054
- Returns whether a screen reader is enabled. This is useful when you want to render different output for screen readers.
2452
+ A React hook that returns whether a screen reader is enabled.
2453
+ This is useful when you want to render different output for screen readers.
2055
2454
 
2056
2455
  ```jsx
2057
2456
  import {useIsScreenReaderEnabled, Text} from 'ink';
@@ -2069,6 +2468,65 @@ const Example = () => {
2069
2468
  };
2070
2469
  ```
2071
2470
 
2471
+ ### useAnimation(options?)
2472
+
2473
+ A React hook that drives animations. Returns a frame counter, elapsed time, frame delta, and a reset function. All animations share a single timer internally, so multiple animated components consolidate into one render cycle.
2474
+
2475
+ ```jsx
2476
+ import {Text, useAnimation} from 'ink';
2477
+
2478
+ const Spinner = () => {
2479
+ const {frame} = useAnimation({interval: 80});
2480
+ const characters = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
2481
+
2482
+ return <Text>{characters[frame % characters.length]}</Text>;
2483
+ };
2484
+ ```
2485
+
2486
+ #### options
2487
+
2488
+ Type: `object`
2489
+
2490
+ ##### interval
2491
+
2492
+ Type: `number`\
2493
+ Default: `100`
2494
+
2495
+ Time between ticks in milliseconds.
2496
+
2497
+ ##### isActive
2498
+
2499
+ Type: `boolean`\
2500
+ Default: `true`
2501
+
2502
+ Whether the animation is running. When set to `false`, the animation stops. When toggled back to `true`, all values reset to `0`.
2503
+
2504
+ #### Return value
2505
+
2506
+ ##### frame
2507
+
2508
+ Type: `number`
2509
+
2510
+ Discrete counter that increments by 1 each interval. Useful for indexed sequences like spinner frames.
2511
+
2512
+ ##### time
2513
+
2514
+ Type: `number`
2515
+
2516
+ Total elapsed time in milliseconds since the animation started or was last reset. Useful for continuous math-based animations like sine waves: `Math.sin(time / 1000 * Math.PI * 2)`.
2517
+
2518
+ ##### delta
2519
+
2520
+ Type: `number`
2521
+
2522
+ Time in milliseconds since the previous rendered tick. Accounts for throttled renders. Useful for physics-based or velocity-driven motion: `position += speed * delta`.
2523
+
2524
+ ##### reset
2525
+
2526
+ Type: `() => void`
2527
+
2528
+ Resets `frame`, `time`, and `delta` to `0` and restarts timing from the current moment. Useful for one-shot animations triggered by events.
2529
+
2072
2530
  ## API
2073
2531
 
2074
2532
  #### render(tree, options?)
@@ -2079,7 +2537,7 @@ Mount a component and render the output.
2079
2537
 
2080
2538
  ##### tree
2081
2539
 
2082
- Type: `ReactElement`
2540
+ Type: `ReactNode`
2083
2541
 
2084
2542
  ##### options
2085
2543
 
@@ -2123,6 +2581,8 @@ Patch console methods to ensure console output doesn't mix with Ink's output.
2123
2581
  When any of the `console.*` methods are called (like `console.log()`), Ink intercepts their output, clears the main output, renders output from the console method, and then rerenders the main output again.
2124
2582
  That way, both are visible and don't overlap each other.
2125
2583
 
2584
+ Once unmount starts, Ink restores the native console before React cleanup runs. Teardown-time `console.*` output then follows the normal console behavior instead of being rerouted through Ink.
2585
+
2126
2586
  This functionality is powered by [patch-console](https://github.com/vadimdemedes/patch-console), so if you need to disable Ink's interception of output but want to build something custom, you can use that.
2127
2587
 
2128
2588
  ###### onRender
@@ -2130,7 +2590,16 @@ This functionality is powered by [patch-console](https://github.com/vadimdemedes
2130
2590
  Type: `({renderTime: number}) => void`\
2131
2591
  Default: `undefined`
2132
2592
 
2133
- Runs the given callback after each render and re-render with a metrics object.
2593
+ Runs the given callback after each render and re-render with render metrics.
2594
+ This callback runs after Ink commits a frame, but it does not wait for `stdout`/`stderr` stream callbacks.
2595
+ To run code after output is flushed, use [`waitUntilRenderFlush()`](#waituntilrenderflush).
2596
+
2597
+ ###### isScreenReaderEnabled
2598
+
2599
+ Type: `boolean`\
2600
+ Default: `process.env['INK_SCREEN_READER'] === 'true'`
2601
+
2602
+ Enable screen reader support. See [Screen Reader Support](#screen-reader-support).
2134
2603
 
2135
2604
  ###### debug
2136
2605
 
@@ -2165,6 +2634,7 @@ Default: `false`
2165
2634
  Enable React Concurrent Rendering mode.
2166
2635
 
2167
2636
  When enabled:
2637
+
2168
2638
  - Suspense boundaries work correctly with async data fetching
2169
2639
  - `useTransition` and `useDeferredValue` hooks are fully functional
2170
2640
  - Updates can be interrupted for higher priority work
@@ -2173,7 +2643,48 @@ When enabled:
2173
2643
  render(<MyApp />, {concurrent: true});
2174
2644
  ```
2175
2645
 
2176
- **Note:** Concurrent mode changes the timing of renders. Some tests may need to use `act()` to properly await updates. The `concurrent` option only takes effect on the first render for a given stdout. If you need to change the rendering mode, call `unmount()` first.
2646
+ > [!NOTE]
2647
+ > Concurrent mode changes the timing of renders. Some tests may need to use `act()` to properly await updates. Reusing the same stdout across multiple `render()` calls without unmounting is unsupported. Call `unmount()` first if you need to change the rendering mode or create a fresh instance.
2648
+
2649
+ ###### interactive
2650
+
2651
+ Type: `boolean`\
2652
+ Default: `true` (`false` if in CI (detected via [`is-in-ci`](https://github.com/sindresorhus/is-in-ci)) or `stdout.isTTY` is falsy)
2653
+
2654
+ Override automatic interactive mode detection.
2655
+
2656
+ By default, Ink detects whether the environment is interactive based on CI detection and `stdout.isTTY`. When non-interactive, Ink skips terminal-specific features like ANSI erase sequences, cursor manipulation, synchronized output, resize handling, and kitty keyboard auto-detection. Only the final frame of non-static output is written at unmount.
2657
+
2658
+ Most users should not need to set this option. Use it when you have your own "interactive" detection logic that differs from the built-in behavior.
2659
+
2660
+ > [!NOTE]
2661
+ > Reusing the same stdout across multiple `render()` calls without unmounting is unsupported. Call `unmount()` first if you need to change this option or create a fresh instance.
2662
+
2663
+ ```jsx
2664
+ // Use your own detection logic
2665
+ const isInteractive = myCustomDetection();
2666
+ render(<MyApp />, {interactive: isInteractive});
2667
+ ```
2668
+
2669
+ ###### alternateScreen
2670
+
2671
+ Type: `boolean`\
2672
+ Default: `false`
2673
+
2674
+ Render the app in the terminal's alternate screen buffer. When enabled, the app renders on a separate screen, and the original terminal content is restored when the app exits. This is the same mechanism used by programs like vim, htop, and less.
2675
+
2676
+ Note: The terminal's scrollback buffer is not available while in the alternate screen. This is standard terminal behavior; programs like vim use the alternate screen specifically to avoid polluting the user's scrollback history.
2677
+
2678
+ Ink intentionally treats alternate-screen teardown output as disposable. It does not preserve or replay teardown-time frames, hook writes, or `console.*` output after restoring the primary screen.
2679
+
2680
+ Only works in interactive mode. Ignored when `interactive` is `false` or in a non-interactive environment (CI, piped stdout).
2681
+
2682
+ > [!NOTE]
2683
+ > Reusing the same stdout across multiple `render()` calls without unmounting is unsupported. Call `unmount()` first if you need to change this option or create a fresh instance.
2684
+
2685
+ ```jsx
2686
+ render(<MyApp />, {alternateScreen: true});
2687
+ ```
2177
2688
 
2178
2689
  ###### kittyKeyboard
2179
2690
 
@@ -2216,6 +2727,7 @@ Default: `['disambiguateEscapeCodes']`
2216
2727
  Protocol flags to request from the terminal. Pass an array of flag name strings.
2217
2728
 
2218
2729
  Available flags:
2730
+
2219
2731
  - `'disambiguateEscapeCodes'` - Disambiguate escape codes
2220
2732
  - `'reportEventTypes'` - Report key press, repeat, and release events
2221
2733
  - `'reportAlternateKeys'` - Report alternate key encodings
@@ -2234,6 +2746,56 @@ When the kitty keyboard protocol is enabled, input handling changes in several w
2234
2746
  - `Escape` key vs `Ctrl+[` - these are disambiguated.
2235
2747
  - **Event types.** With the `reportEventTypes` flag, key press, repeat, and release events are distinguished via `key.eventType`.
2236
2748
 
2749
+ #### renderToString(tree, options?)
2750
+
2751
+ Returns: `string`
2752
+
2753
+ Render a React element to a string synchronously. Unlike `render()`, this function does not write to stdout, does not set up any terminal event listeners, and returns the rendered output as a string.
2754
+
2755
+ Useful for generating documentation, writing output to files, testing, or any scenario where you need the rendered output as a string without starting a persistent terminal application.
2756
+
2757
+ ```jsx
2758
+ import {renderToString, Text, Box} from 'ink';
2759
+
2760
+ const output = renderToString(
2761
+ <Box padding={1}>
2762
+ <Text color="green">Hello World</Text>
2763
+ </Box>,
2764
+ );
2765
+
2766
+ console.log(output);
2767
+ ```
2768
+
2769
+ **Notes:**
2770
+
2771
+ - Terminal-specific hooks (`useInput`, `useStdin`, `useStdout`, `useStderr`, `useWindowSize`, `useApp`, `useFocus`, `useFocusManager`) return default no-op values since there is no terminal session. They will not throw, but they will not function as in a live terminal.
2772
+ - `useEffect` callbacks will execute during rendering (due to synchronous rendering mode), but state updates they trigger will not affect the returned output, which reflects the initial render.
2773
+ - `useLayoutEffect` callbacks fire synchronously during commit, so state updates they trigger **will** be reflected in the output.
2774
+ - The `<Static>` component is supported — its output is prepended to the dynamic output.
2775
+ - If a component throws during rendering, the error is propagated to the caller after cleanup.
2776
+
2777
+ ##### tree
2778
+
2779
+ Type: `ReactNode`
2780
+
2781
+ ##### options
2782
+
2783
+ Type: `object`
2784
+
2785
+ ###### columns
2786
+
2787
+ Type: `number`\
2788
+ Default: `80`
2789
+
2790
+ Width of the virtual terminal in columns. Controls where text wrapping occurs.
2791
+
2792
+ ```jsx
2793
+ const output = renderToString(<Text>{'A'.repeat(100)}</Text>, {
2794
+ columns: 40,
2795
+ });
2796
+ // Text wraps at 40 columns
2797
+ ```
2798
+
2237
2799
  #### Instance
2238
2800
 
2239
2801
  This is the object that `render()` returns.
@@ -2244,7 +2806,7 @@ Replace the previous root node with a new one or update the props of the current
2244
2806
 
2245
2807
  ###### tree
2246
2808
 
2247
- Type: `ReactElement`
2809
+ Type: `ReactNode`
2248
2810
 
2249
2811
  ```jsx
2250
2812
  // Update props of the root node
@@ -2267,7 +2829,10 @@ unmount();
2267
2829
 
2268
2830
  ##### waitUntilExit()
2269
2831
 
2270
- Returns a promise that resolves when the app is unmounted.
2832
+ Returns a promise that settles when the app is unmounted.
2833
+
2834
+ It resolves with the value passed to `exit(value)` and rejects with the error passed to `exit(error)`.
2835
+ When `unmount()` is called manually, it settles after unmount-related stdout writes complete.
2271
2836
 
2272
2837
  ```jsx
2273
2838
  const {unmount, waitUntilExit} = render(<MyApp />);
@@ -2277,6 +2842,27 @@ setTimeout(unmount, 1000);
2277
2842
  await waitUntilExit(); // resolves after `unmount()` is called
2278
2843
  ```
2279
2844
 
2845
+ ##### waitUntilRenderFlush()
2846
+
2847
+ Returns a promise that settles after pending render output is flushed to stdout.
2848
+
2849
+ Useful when you need to run code only after a frame is written:
2850
+
2851
+ ```jsx
2852
+ const {rerender, waitUntilRenderFlush} = render(<MyApp step="loading" />);
2853
+
2854
+ rerender(<MyApp step="ready" />);
2855
+ await waitUntilRenderFlush(); // output for "ready" is flushed
2856
+
2857
+ runNextCommand();
2858
+ ```
2859
+
2860
+ ##### cleanup()
2861
+
2862
+ Unmount the current app and delete the internal Ink instance associated with the current `stdout`.
2863
+ This is mostly useful for advanced cases (for example, tests) where you need `render()` to create a fresh instance for the same stream.
2864
+ Unlike deleting the internal instance directly, this also tears down terminal state such as the alternate screen.
2865
+
2280
2866
  ##### clear()
2281
2867
 
2282
2868
  Clear output.
@@ -2292,7 +2878,8 @@ Measure the dimensions of a particular `<Box>` element.
2292
2878
  Returns an object with `width` and `height` properties.
2293
2879
  This function is useful when your component needs to know the amount of available space it has. You can use it when you need to change the layout based on the length of its content.
2294
2880
 
2295
- **Note:** `measureElement()` returns correct results only after the initial render, when the layout has been calculated. Until then, `width` and `height` equal zero. It's recommended to call `measureElement()` in a `useEffect` hook, which fires after the component has rendered.
2881
+ > [!NOTE]
2882
+ > `measureElement()` returns `{width: 0, height: 0}` when called during render (before layout is calculated). Call it from post-render code, such as `useEffect`, `useLayoutEffect`, input handlers, or timer callbacks. When content changes, pass the relevant dependency to your effect so it re-measures after each update.
2296
2883
 
2297
2884
  ##### ref
2298
2885
 
@@ -2361,7 +2948,8 @@ npx react-devtools
2361
2948
  After it starts, you should see the component tree of your CLI.
2362
2949
  You can even inspect and change the props of components, and see the results immediately in the CLI, without restarting it.
2363
2950
 
2364
- **Note**: You must manually quit your CLI via <kbd>Ctrl</kbd>+<kbd>C</kbd> after you're done testing.
2951
+ > [!NOTE]
2952
+ > You must manually quit your CLI via <kbd>Ctrl</kbd>+<kbd>C</kbd> after you're done testing.
2365
2953
 
2366
2954
  ## Screen Reader Support
2367
2955
 
@@ -2417,38 +3005,49 @@ Default: `false`
2417
3005
 
2418
3006
  Hide the element from screen readers.
2419
3007
 
2420
- ##### aria-role
3008
+ ### `aria-role`
2421
3009
 
2422
3010
  Type: `string`
2423
3011
 
2424
3012
  The role of the element.
2425
3013
 
2426
3014
  Supported values:
3015
+
2427
3016
  - `button`
2428
3017
  - `checkbox`
2429
- - `radio`
2430
- - `radiogroup`
3018
+ - `combobox`
2431
3019
  - `list`
3020
+ - `listbox`
2432
3021
  - `listitem`
2433
3022
  - `menu`
2434
3023
  - `menuitem`
3024
+ - `option`
2435
3025
  - `progressbar`
3026
+ - `radio`
3027
+ - `radiogroup`
2436
3028
  - `tab`
2437
3029
  - `tablist`
3030
+ - `table`
3031
+ - `textbox`
2438
3032
  - `timer`
2439
3033
  - `toolbar`
2440
- - `table`
2441
3034
 
2442
- ##### aria-state
3035
+ ### `aria-state`
2443
3036
 
2444
3037
  Type: `object`
2445
3038
 
2446
3039
  The state of the element.
2447
3040
 
2448
3041
  Supported values:
3042
+
3043
+ - `busy` (boolean)
2449
3044
  - `checked` (boolean)
2450
3045
  - `disabled` (boolean)
2451
3046
  - `expanded` (boolean)
3047
+ - `multiline` (boolean)
3048
+ - `multiselectable` (boolean)
3049
+ - `readonly` (boolean)
3050
+ - `required` (boolean)
2452
3051
  - `selected` (boolean)
2453
3052
 
2454
3053
  ## Creating Components
@@ -2491,11 +3090,16 @@ For a practical example of building an accessible component, see the [ARIA examp
2491
3090
  - [ink-scroll-list](https://github.com/ByteLandTechnology/ink-scroll-list) - Scrollable list.
2492
3091
  - [ink-stepper](https://github.com/archcorsair/ink-stepper) - Step-by-step wizard.
2493
3092
  - [ink-virtual-list](https://github.com/archcorsair/ink-virtual-list) - Virtualized list that renders only visible items for performance.
3093
+ - [ink-color-picker](https://github.com/sina-byn/ink-color-picker) - Color picker.
2494
3094
 
2495
3095
  ## Useful Hooks
2496
3096
 
2497
3097
  - [ink-use-stdout-dimensions](https://github.com/cameronhunter/ink-monorepo/tree/master/packages/ink-use-stdout-dimensions) - Subscribe to stdout dimensions.
2498
3098
 
3099
+ ## Recipes
3100
+
3101
+ - [Routing with React Router](recipes/routing.md) - Navigate between routes using `MemoryRouter`.
3102
+
2499
3103
  ## Examples
2500
3104
 
2501
3105
  The [`examples`](/examples) directory contains a set of real examples. You can run them with:
@@ -2517,6 +3121,20 @@ npm run example examples/[example name]
2517
3121
  - [Write to stderr](examples/use-stderr/use-stderr.tsx) - Write to stderr, bypassing main Ink output.
2518
3122
  - [Static](examples/static/static.tsx) - Use the `<Static>` component to render permanent output.
2519
3123
  - [Child process](examples/subprocess-output) - Renders output from a child process.
3124
+ - [Router](examples/router/router.tsx) - Navigate between routes using React Router's `MemoryRouter`.
3125
+
3126
+ ## Continuous Integration
3127
+
3128
+ When running on CI (detected via the `CI` environment variable), Ink adapts its rendering:
3129
+
3130
+ - Only the last frame is rendered on exit, instead of continuously updating the terminal. This is because most CI environments don't support the ANSI escape sequences used to overwrite previous output.
3131
+ - Terminal resize events are not listened to.
3132
+
3133
+ If your CI environment supports full terminal rendering and you want to opt out of this behavior, set `CI=false`:
3134
+
3135
+ ```sh
3136
+ CI=false node my-cli.js
3137
+ ```
2520
3138
 
2521
3139
  ## Maintainers
2522
3140