ink 6.6.0 → 6.8.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 (73) 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/App.d.ts +8 -49
  5. package/build/components/App.js +293 -228
  6. package/build/components/App.js.map +1 -1
  7. package/build/components/AppContext.d.ts +5 -1
  8. package/build/components/AppContext.js.map +1 -1
  9. package/build/components/Cursor.d.ts +83 -0
  10. package/build/components/Cursor.js +53 -0
  11. package/build/components/Cursor.js.map +1 -0
  12. package/build/components/CursorContext.d.ts +11 -0
  13. package/build/components/CursorContext.js +8 -0
  14. package/build/components/CursorContext.js.map +1 -0
  15. package/build/components/ErrorBoundary.d.ts +18 -0
  16. package/build/components/ErrorBoundary.js +23 -0
  17. package/build/components/ErrorBoundary.js.map +1 -0
  18. package/build/cursor-helpers.d.ts +38 -0
  19. package/build/cursor-helpers.js +56 -0
  20. package/build/cursor-helpers.js.map +1 -0
  21. package/build/dom.js +5 -4
  22. package/build/dom.js.map +1 -1
  23. package/build/hooks/use-cursor.d.ts +12 -0
  24. package/build/hooks/use-cursor.js +29 -0
  25. package/build/hooks/use-cursor.js.map +1 -0
  26. package/build/hooks/use-input.d.ts +30 -0
  27. package/build/hooks/use-input.js +31 -2
  28. package/build/hooks/use-input.js.map +1 -1
  29. package/build/index.d.ts +6 -0
  30. package/build/index.js +3 -0
  31. package/build/index.js.map +1 -1
  32. package/build/ink.d.ts +39 -3
  33. package/build/ink.js +377 -49
  34. package/build/ink.js.map +1 -1
  35. package/build/input-parser.d.ts +7 -0
  36. package/build/input-parser.js +154 -0
  37. package/build/input-parser.js.map +1 -0
  38. package/build/kitty-keyboard.d.ts +23 -0
  39. package/build/kitty-keyboard.js +32 -0
  40. package/build/kitty-keyboard.js.map +1 -0
  41. package/build/layout.d.ts +7 -0
  42. package/build/layout.js +33 -0
  43. package/build/layout.js.map +1 -0
  44. package/build/log-update.d.ts +6 -1
  45. package/build/log-update.js +163 -40
  46. package/build/log-update.js.map +1 -1
  47. package/build/output.d.ts +1 -0
  48. package/build/output.js +38 -5
  49. package/build/output.js.map +1 -1
  50. package/build/parse-keypress.d.ts +8 -0
  51. package/build/parse-keypress.js +270 -2
  52. package/build/parse-keypress.js.map +1 -1
  53. package/build/reconciler.js +23 -3
  54. package/build/reconciler.js.map +1 -1
  55. package/build/render-to-string.d.ts +38 -0
  56. package/build/render-to-string.js +115 -0
  57. package/build/render-to-string.js.map +1 -0
  58. package/build/render.d.ts +34 -1
  59. package/build/render.js +7 -2
  60. package/build/render.js.map +1 -1
  61. package/build/sanitize-ansi.d.ts +2 -0
  62. package/build/sanitize-ansi.js +27 -0
  63. package/build/sanitize-ansi.js.map +1 -0
  64. package/build/squash-text-nodes.js +2 -1
  65. package/build/squash-text-nodes.js.map +1 -1
  66. package/build/utils.d.ts +2 -0
  67. package/build/utils.js +4 -0
  68. package/build/utils.js.map +1 -0
  69. package/build/write-synchronized.d.ts +4 -0
  70. package/build/write-synchronized.js +7 -0
  71. package/build/write-synchronized.js.map +1 -0
  72. package/package.json +27 -21
  73. package/readme.md +292 -14
package/readme.md CHANGED
@@ -69,13 +69,11 @@ render(<Counter />);
69
69
 
70
70
  <img src="media/demo.svg" width="600">
71
71
 
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
72
  ## Who's Using Ink?
75
73
 
76
74
  - [Claude Code](https://github.com/anthropics/claude-code) - An agentic coding tool made by Anthropic.
77
75
  - [Gemini CLI](https://github.com/google-gemini/gemini-cli) - An agentic coding tool made by Google.
78
- - [GitHub Copilot for CLI](https://githubnext.com/projects/copilot-cli) - Just say what you want the shell to do.
76
+ - [GitHub Copilot CLI](https://github.com/features/copilot/cli) - Just say what you want the shell to do.
79
77
  - [Canva CLI](https://www.canva.dev/docs/apps/canva-cli/) - CLI for creating and managing Canva Apps.
80
78
  - [Cloudflare's Wrangler](https://github.com/cloudflare/wrangler2) - The CLI for Cloudflare Workers.
81
79
  - [Linear](https://linear.app) - Linear built an internal CLI for managing deployments, configs, and other housekeeping tasks.
@@ -123,15 +121,18 @@ Feel free to play around with the code and fork this Repl at [https://repl.it/@v
123
121
  - [argonaut](https://github.com/darksworm/argonaut) - Manage Argo CD resources.
124
122
  - [Qodo Command](https://github.com/qodo-ai/command) - Build, run, and manage AI agents.
125
123
  - [Nanocoder](https://github.com/nano-collective/nanocoder) - A community-built, local-first AI coding agent with multi-provider support.
124
+ - [dev3000](https://github.com/vercel-labs/dev3000) - An AI agent MCP orchestrator and developer browser.
126
125
  - [Neovate Code](https://github.com/neovateai/neovate-code) - An agentic coding tool made by AntGroup.
127
126
  - [instagram-cli](https://github.com/supreme-gg-gg/instagram-cli) - Instagram client.
128
127
  - [ElevenLabs CLI](https://github.com/elevenlabs/cli) - ElevenLabs agents client.
128
+ - [SSH AI Chat](https://github.com/miantiao-me/ssh-ai-chat) - Chat with AI over SSH.
129
129
 
130
130
  *(PRs welcome. Append new entries at the end. Repos must have 100+ stars and showcase Ink beyond a basic list picker.)*
131
131
 
132
132
  ## Contents
133
133
 
134
134
  - [Getting Started](#getting-started)
135
+ - [App Lifecycle](#app-lifecycle)
135
136
  - [Components](#components)
136
137
  - [`<Text>`](#text)
137
138
  - [`<Box>`](#box)
@@ -147,13 +148,16 @@ Feel free to play around with the code and fork this Repl at [https://repl.it/@v
147
148
  - [`useStderr`](#usestderr)
148
149
  - [`useFocus`](#usefocusoptions)
149
150
  - [`useFocusManager`](#usefocusmanager)
151
+ - [`useCursor`](#usecursor)
150
152
  - [API](#api)
151
153
  - [Testing](#testing)
152
154
  - [Using React Devtools](#using-react-devtools)
153
155
  - [Screen Reader Support](#screen-reader-support)
154
156
  - [Useful Components](#useful-components)
155
157
  - [Useful Hooks](#useful-hooks)
158
+ - [Recipes](#recipes)
156
159
  - [Examples](#examples)
160
+ - [Continuous Integration](#continuous-integration)
157
161
 
158
162
  ## Getting Started
159
163
 
@@ -220,6 +224,22 @@ Think of it as if every `<div>` in the browser had `display: flex`.
220
224
  See [`<Box>`](#box) built-in component below for documentation on how to use Flexbox layouts in Ink.
221
225
  Note that all text must be wrapped in a [`<Text>`](#text) component.
222
226
 
227
+ ## App Lifecycle
228
+
229
+ 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.
230
+
231
+ 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).
232
+
233
+ Use [`waitUntilExit()`](#waituntilexit) to run code after the app is unmounted:
234
+
235
+ ```jsx
236
+ const {waitUntilExit} = render(<MyApp />);
237
+
238
+ await waitUntilExit();
239
+
240
+ console.log('App exited');
241
+ ```
242
+
223
243
  ## Components
224
244
 
225
245
  ### `<Text>`
@@ -1417,12 +1437,11 @@ For example, to implement a hanging indent component, you can indent all the lin
1417
1437
  ```jsx
1418
1438
  import {render, Transform} from 'ink';
1419
1439
 
1420
- const HangingIndent = ({content, indent = 4, children, ...props}) => (
1440
+ const HangingIndent = ({indent = 4, children}) => (
1421
1441
  <Transform
1422
1442
  transform={(line, index) =>
1423
1443
  index === 0 ? line : ' '.repeat(indent) + line
1424
1444
  }
1425
- {...props}
1426
1445
  >
1427
1446
  {children}
1428
1447
  </Transform>
@@ -1436,9 +1455,8 @@ const text =
1436
1455
  'of my hands only. I lived there two years and two months. At ' +
1437
1456
  'present I am a sojourner in civilized life again.';
1438
1457
 
1439
- // Other text properties are allowed as well
1440
1458
  render(
1441
- <HangingIndent bold dimColor indent={4}>
1459
+ <HangingIndent indent={4}>
1442
1460
  {text}
1443
1461
  </HangingIndent>
1444
1462
  );
@@ -1582,6 +1600,16 @@ Default: `false`
1582
1600
  If the Page Up or Page Down key was pressed, the corresponding property will be `true`.
1583
1601
  For example, if the user presses Page Down, `key.pageDown` equals `true`.
1584
1602
 
1603
+ ###### key.home
1604
+
1605
+ ###### key.end
1606
+
1607
+ Type: `boolean`\
1608
+ Default: `false`
1609
+
1610
+ If the Home or End key was pressed, the corresponding property will be `true`.
1611
+ For example, if the user presses End, `key.end` equals `true`.
1612
+
1585
1613
  ###### key.meta
1586
1614
 
1587
1615
  Type: `boolean`\
@@ -1589,6 +1617,41 @@ Default: `false`
1589
1617
 
1590
1618
  [Meta key](https://en.wikipedia.org/wiki/Meta_key) was pressed.
1591
1619
 
1620
+ ###### key.super
1621
+
1622
+ Type: `boolean`\
1623
+ Default: `false`
1624
+
1625
+ Super key (Cmd on macOS, Win on Windows) was pressed. Requires [kitty keyboard protocol](#kittykeyboard).
1626
+
1627
+ ###### key.hyper
1628
+
1629
+ Type: `boolean`\
1630
+ Default: `false`
1631
+
1632
+ Hyper key was pressed. Requires [kitty keyboard protocol](#kittykeyboard).
1633
+
1634
+ ###### key.capsLock
1635
+
1636
+ Type: `boolean`\
1637
+ Default: `false`
1638
+
1639
+ Caps Lock was active. Requires [kitty keyboard protocol](#kittykeyboard).
1640
+
1641
+ ###### key.numLock
1642
+
1643
+ Type: `boolean`\
1644
+ Default: `false`
1645
+
1646
+ Num Lock was active. Requires [kitty keyboard protocol](#kittykeyboard).
1647
+
1648
+ ###### key.eventType
1649
+
1650
+ Type: `'press' | 'repeat' | 'release'`\
1651
+ Default: `undefined`
1652
+
1653
+ The type of key event. Only available with [kitty keyboard protocol](#kittykeyboard). Without the protocol, this property is `undefined`.
1654
+
1592
1655
  #### options
1593
1656
 
1594
1657
  Type: `object`
@@ -1605,17 +1668,20 @@ Useful when there are multiple `useInput` hooks used at once to avoid handling t
1605
1668
 
1606
1669
  `useApp` is a React hook that exposes a method to manually exit the app (unmount).
1607
1670
 
1608
- #### exit(error?)
1671
+ #### exit(errorOrResult?)
1609
1672
 
1610
1673
  Type: `Function`
1611
1674
 
1612
1675
  Exit (unmount) the whole Ink app.
1613
1676
 
1614
- ##### error
1677
+ ##### errorOrResult
1615
1678
 
1616
- Type: `Error`
1679
+ Type: `Error | unknown`
1617
1680
 
1618
- Optional error. If passed, [`waitUntilExit`](waituntilexit) will reject with that error.
1681
+ Optional value that controls how [`waitUntilExit`](waituntilexit) settles:
1682
+ - `exit()` resolves with `undefined`.
1683
+ - `exit(error)` rejects when `error` is an `Error`.
1684
+ - `exit(value)` resolves with `value`.
1619
1685
 
1620
1686
  ```js
1621
1687
  import {useApp} from 'ink';
@@ -1962,6 +2028,55 @@ const Example = () => {
1962
2028
  };
1963
2029
  ```
1964
2030
 
2031
+ ### useCursor()
2032
+
2033
+ `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.
2034
+
2035
+ ```jsx
2036
+ import {useState} from 'react';
2037
+ import {Box, Text, useCursor} from 'ink';
2038
+ import stringWidth from 'string-width';
2039
+
2040
+ const TextInput = () => {
2041
+ const [text, setText] = useState('');
2042
+ const {setCursorPosition} = useCursor();
2043
+
2044
+ const prompt = '> ';
2045
+ setCursorPosition({x: stringWidth(prompt + text), y: 1});
2046
+
2047
+ return (
2048
+ <Box flexDirection="column">
2049
+ <Text>Type here:</Text>
2050
+ <Text>{prompt}{text}</Text>
2051
+ </Box>
2052
+ );
2053
+ };
2054
+ ```
2055
+
2056
+ #### setCursorPosition(position)
2057
+
2058
+ Set the cursor position relative to the Ink output. Pass `undefined` to hide the cursor.
2059
+
2060
+ ##### position
2061
+
2062
+ Type: `object | undefined`
2063
+
2064
+ Use [`string-width`](https://github.com/sindresorhus/string-width) to calculate `x` for strings containing wide characters (CJK, emoji).
2065
+
2066
+ See a full example at [examples/cursor-ime](examples/cursor-ime/cursor-ime.tsx).
2067
+
2068
+ ###### x
2069
+
2070
+ Type: `number`
2071
+
2072
+ Column position (0-based).
2073
+
2074
+ ###### y
2075
+
2076
+ Type: `number`
2077
+
2078
+ Row position from the top of the Ink output (0 = first line).
2079
+
1965
2080
  ### useIsScreenReaderEnabled()
1966
2081
 
1967
2082
  Returns whether a screen reader is enabled. This is useful when you want to render different output for screen readers.
@@ -1992,7 +2107,7 @@ Mount a component and render the output.
1992
2107
 
1993
2108
  ##### tree
1994
2109
 
1995
- Type: `ReactElement`
2110
+ Type: `ReactNode`
1996
2111
 
1997
2112
  ##### options
1998
2113
 
@@ -2045,6 +2160,13 @@ Default: `undefined`
2045
2160
 
2046
2161
  Runs the given callback after each render and re-render with a metrics object.
2047
2162
 
2163
+ ###### isScreenReaderEnabled
2164
+
2165
+ Type: `boolean`\
2166
+ Default: `process.env['INK_SCREEN_READER'] === 'true'`
2167
+
2168
+ Enable screen reader support. See [Screen Reader Support](#screen-reader-support).
2169
+
2048
2170
  ###### debug
2049
2171
 
2050
2172
  Type: `boolean`\
@@ -2070,6 +2192,133 @@ Default: `false`
2070
2192
  Enable incremental rendering mode which only updates changed lines instead of redrawing the entire output.
2071
2193
  This can reduce flickering and improve performance for frequently updating UIs.
2072
2194
 
2195
+ ###### concurrent
2196
+
2197
+ Type: `boolean`\
2198
+ Default: `false`
2199
+
2200
+ Enable React Concurrent Rendering mode.
2201
+
2202
+ When enabled:
2203
+ - Suspense boundaries work correctly with async data fetching
2204
+ - `useTransition` and `useDeferredValue` hooks are fully functional
2205
+ - Updates can be interrupted for higher priority work
2206
+
2207
+ ```jsx
2208
+ render(<MyApp />, {concurrent: true});
2209
+ ```
2210
+
2211
+ **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.
2212
+
2213
+ ###### kittyKeyboard
2214
+
2215
+ Type: `object`\
2216
+ Default: `undefined`
2217
+
2218
+ Enable the [kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/) for enhanced keyboard input handling. When enabled, terminals that support the protocol will report additional key information including `super`, `hyper`, `capsLock`, `numLock` modifiers and `eventType` (press/repeat/release).
2219
+
2220
+ ```jsx
2221
+ import {render} from 'ink';
2222
+
2223
+ render(<MyApp />, {kittyKeyboard: {mode: 'auto'}});
2224
+ ```
2225
+
2226
+ ```jsx
2227
+ import {render} from 'ink';
2228
+
2229
+ render(<MyApp />, {
2230
+ kittyKeyboard: {
2231
+ mode: 'enabled',
2232
+ flags: ['disambiguateEscapeCodes', 'reportEventTypes'],
2233
+ },
2234
+ });
2235
+ ```
2236
+
2237
+ **kittyKeyboard.mode**
2238
+
2239
+ Type: `'auto' | 'enabled' | 'disabled'`\
2240
+ Default: `'auto'`
2241
+
2242
+ - `'auto'`: Detect terminal support using a heuristic precheck (known terminals like kitty, WezTerm, Ghostty) followed by a protocol query confirmation (`CSI ? u`). The protocol is only enabled if the terminal responds to the query within a short timeout.
2243
+ - `'enabled'`: Force enable the protocol. Both stdin and stdout must be TTYs.
2244
+ - `'disabled'`: Never enable the protocol.
2245
+
2246
+ **kittyKeyboard.flags**
2247
+
2248
+ Type: `string[]`\
2249
+ Default: `['disambiguateEscapeCodes']`
2250
+
2251
+ Protocol flags to request from the terminal. Pass an array of flag name strings.
2252
+
2253
+ Available flags:
2254
+ - `'disambiguateEscapeCodes'` - Disambiguate escape codes
2255
+ - `'reportEventTypes'` - Report key press, repeat, and release events
2256
+ - `'reportAlternateKeys'` - Report alternate key encodings
2257
+ - `'reportAllKeysAsEscapeCodes'` - Report all keys as escape codes
2258
+ - `'reportAssociatedText'` - Report associated text with key events
2259
+
2260
+ **Behavior notes**
2261
+
2262
+ When the kitty keyboard protocol is enabled, input handling changes in several ways:
2263
+
2264
+ - **Non-printable keys produce empty input.** Keys like function keys (F1-F35), modifier-only keys (Shift, Control, Super), media keys, Caps Lock, Print Screen, and similar keys will not produce any text in the `input` parameter of `useInput`. They can still be detected via the `key` object properties.
2265
+ - **Ctrl+letter shortcuts work as expected.** When the terminal sends `Ctrl+letter` as codepoint 1-26 (the kitty CSI-u alternate form), `input` is set to the letter name (e.g. `'c'` for `Ctrl+C`) and `key.ctrl` is `true`. This ensures `exitOnCtrlC` and custom `Ctrl+letter` handlers continue to work regardless of which codepoint form the terminal uses.
2266
+ - **Key disambiguation.** The protocol allows the terminal to distinguish between keys that normally produce the same escape sequence. For example:
2267
+ - `Ctrl+I` vs `Tab` - without the protocol, both produce the same byte (`\x09`). With the protocol, they are reported as distinct keys.
2268
+ - `Shift+Enter` vs `Enter` - the shift modifier is correctly reported.
2269
+ - `Escape` key vs `Ctrl+[` - these are disambiguated.
2270
+ - **Event types.** With the `reportEventTypes` flag, key press, repeat, and release events are distinguished via `key.eventType`.
2271
+
2272
+ #### renderToString(tree, options?)
2273
+
2274
+ Returns: `string`
2275
+
2276
+ 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.
2277
+
2278
+ 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.
2279
+
2280
+ ```jsx
2281
+ import {renderToString, Text, Box} from 'ink';
2282
+
2283
+ const output = renderToString(
2284
+ <Box padding={1}>
2285
+ <Text color="green">Hello World</Text>
2286
+ </Box>,
2287
+ );
2288
+
2289
+ console.log(output);
2290
+ ```
2291
+
2292
+ **Notes:**
2293
+
2294
+ - Terminal-specific hooks (`useInput`, `useStdin`, `useStdout`, `useStderr`, `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.
2295
+ - `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.
2296
+ - `useLayoutEffect` callbacks fire synchronously during commit, so state updates they trigger **will** be reflected in the output.
2297
+ - The `<Static>` component is supported — its output is prepended to the dynamic output.
2298
+ - If a component throws during rendering, the error is propagated to the caller after cleanup.
2299
+
2300
+ ##### tree
2301
+
2302
+ Type: `ReactNode`
2303
+
2304
+ ##### options
2305
+
2306
+ Type: `object`
2307
+
2308
+ ###### columns
2309
+
2310
+ Type: `number`\
2311
+ Default: `80`
2312
+
2313
+ Width of the virtual terminal in columns. Controls where text wrapping occurs.
2314
+
2315
+ ```jsx
2316
+ const output = renderToString(<Text>{'A'.repeat(100)}</Text>, {
2317
+ columns: 40,
2318
+ });
2319
+ // Text wraps at 40 columns
2320
+ ```
2321
+
2073
2322
  #### Instance
2074
2323
 
2075
2324
  This is the object that `render()` returns.
@@ -2080,7 +2329,7 @@ Replace the previous root node with a new one or update the props of the current
2080
2329
 
2081
2330
  ###### tree
2082
2331
 
2083
- Type: `ReactElement`
2332
+ Type: `ReactNode`
2084
2333
 
2085
2334
  ```jsx
2086
2335
  // Update props of the root node
@@ -2103,7 +2352,9 @@ unmount();
2103
2352
 
2104
2353
  ##### waitUntilExit()
2105
2354
 
2106
- Returns a promise that resolves when the app is unmounted.
2355
+ Returns a promise that settles when the app is unmounted.
2356
+
2357
+ It resolves with the value passed to `exit(value)` and rejects with the error passed to `exit(error)`.
2107
2358
 
2108
2359
  ```jsx
2109
2360
  const {unmount, waitUntilExit} = render(<MyApp />);
@@ -2113,6 +2364,12 @@ setTimeout(unmount, 1000);
2113
2364
  await waitUntilExit(); // resolves after `unmount()` is called
2114
2365
  ```
2115
2366
 
2367
+ ##### cleanup()
2368
+
2369
+ Delete the internal Ink instance associated with the current `stdout`.
2370
+ This is mostly useful for advanced cases (for example, tests) where you need `render()` to create a fresh instance for the same stream.
2371
+ This does not unmount the current app.
2372
+
2116
2373
  ##### clear()
2117
2374
 
2118
2375
  Clear output.
@@ -2325,11 +2582,18 @@ For a practical example of building an accessible component, see the [ARIA examp
2325
2582
  - [ink-chart](https://github.com/pppp606/ink-chart) - Sparkline and bar chart.
2326
2583
  - [ink-scroll-view](https://github.com/ByteLandTechnology/ink-scroll-view) - Scroll container.
2327
2584
  - [ink-scroll-list](https://github.com/ByteLandTechnology/ink-scroll-list) - Scrollable list.
2585
+ - [ink-stepper](https://github.com/archcorsair/ink-stepper) - Step-by-step wizard.
2586
+ - [ink-virtual-list](https://github.com/archcorsair/ink-virtual-list) - Virtualized list that renders only visible items for performance.
2587
+ - [ink-color-picker](https://github.com/sina-byn/ink-color-picker) - Color picker.
2328
2588
 
2329
2589
  ## Useful Hooks
2330
2590
 
2331
2591
  - [ink-use-stdout-dimensions](https://github.com/cameronhunter/ink-monorepo/tree/master/packages/ink-use-stdout-dimensions) - Subscribe to stdout dimensions.
2332
2592
 
2593
+ ## Recipes
2594
+
2595
+ - [Routing with React Router](recipes/routing.md) - Navigate between routes using `MemoryRouter`.
2596
+
2333
2597
  ## Examples
2334
2598
 
2335
2599
  The [`examples`](/examples) directory contains a set of real examples. You can run them with:
@@ -2351,6 +2615,20 @@ npm run example examples/[example name]
2351
2615
  - [Write to stderr](examples/use-stderr/use-stderr.tsx) - Write to stderr, bypassing main Ink output.
2352
2616
  - [Static](examples/static/static.tsx) - Use the `<Static>` component to render permanent output.
2353
2617
  - [Child process](examples/subprocess-output) - Renders output from a child process.
2618
+ - [Router](examples/router/router.tsx) - Navigate between routes using React Router's `MemoryRouter`.
2619
+
2620
+ ## Continuous Integration
2621
+
2622
+ When running on CI (detected via the `CI` environment variable), Ink adapts its rendering:
2623
+
2624
+ - 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.
2625
+ - Terminal resize events are not listened to.
2626
+
2627
+ If your CI environment supports full terminal rendering and you want to opt out of this behavior, set `CI=false`:
2628
+
2629
+ ```sh
2630
+ CI=false node my-cli.js
2631
+ ```
2354
2632
 
2355
2633
  ## Maintainers
2356
2634