Coming from ink
Move an ink program to controlroom/ink by changing its import and installing react-reconciler; graded by ink 8.0.0's own suite at 1304 of 1304. Packages written for ink run unchanged with one package.json line.
controlroom/ink is ink 8.0.0's API, graded by ink's own test suite at 1304 of 1304 cases
and by @inkjs/ui 2.0.0's at 102 of 102 (Compatibility). Your components,
hooks and props stay as they are.
Migrate from ink: one import
- import { render, Box, Text, useInput } from 'ink';
+ import { render, Box, Text, useInput } from 'controlroom/ink';npm install controlroom react-reconciler
npm uninstall inkink installs React's reconciler for you; controlroom/ink renders your React through React's
own reconciler, so you install it yourself. react and react-reconciler are optional peers of
controlroom: if either is missing, controlroom/ink refuses on first import with
E_PEER_MISSING, and its fix is the install line. The rest of controlroom never loads them.
burgee migrate does this for you on a project whose ink is 8.x: it rewrites each 'ink'
import to 'controlroom/ink', and its report's next step is
npm install controlroom@^<version> react-reconciler && npm uninstall ink. A project still on
ink 6 is reported and left alone, because ink 6's suite grades the drop-in at 540 / 584.
It renders the same
A component written for ink, rendered by the drop-in:
import { createElement as h } from 'react';
import { Box, renderToString, Text } from 'controlroom/ink';
const Summary = ({ passed, failed }) =>
h(
Box,
{ borderStyle: 'round', paddingX: 1, flexDirection: 'column' },
h(Text, { bold: true }, 'Test run'),
h(Text, null, `${passed} passed, ${failed} failed`),
);
console.log(renderToString(h(Summary, { passed: 41, failed: 1 }), { columns: 32 }));╭──────────────────────────────╮
│ Test run │
│ 41 passed, 1 failed │
╰──────────────────────────────╯(createElement stands in for JSX so the file runs with plain node; with your JSX build the
component is the one you already have.) The border is flagstaff's, the bold is roundel's, and
the box was laid out by controlroom's port of yoga's flexbox: there is no yoga in the install.
What is in it
render, renderToString, Box, Text, Static, Transform, Newline, Spacer, every
ink hook (useInput, useApp, useFocus, useFocusManager, useCursor, useStdin,
useStdout, useStderr, useIsScreenReaderEnabled, useAnimation, usePaste,
useBoxMetrics and useWindowSize), and measureElement. ink 8's additions are there too: the alternateScreen and interactive
options, wrap="hard", suspendTerminal and waitUntilRenderFlush on useApp, and kitty
keyboard negotiation. The API reference lists every export with its signature.
Packages written for ink
ink-spinner, ink-text-input, ink-select-input and @inkjs/ui import 'ink', not
'controlroom/ink'. They run unchanged when 'ink' resolves to the drop-in. An npm: alias
cannot do that, because it names a package and not a subpath. A two-file package of your own
can:
{
"dependencies": {
"ink": "file:./ink",
"controlroom": "*",
"react": "^19.3.0",
"react-reconciler": "^0.34.0"
}
}// ink/index.js
export * from 'controlroom/ink';// ink/package.json
{ "name": "ink", "version": "8.0.0", "type": "module", "exports": "./index.js" }The version is the ink API the drop-in implements, so every component's peer range on ink is
met and npm installs no other ink.
examples/ink-ecosystem
runs ink-spinner, ink-text-input and ink-select-input that way, as published, and
examples/chat-cli-ink
is a whole Ink chat app on the drop-in. @inkjs/ui's suite passes 102 / 102 the same way.
What changes
- Colour comes from the environment. ink colours through chalk, so setting
chalk.levelon the chalk module ink shares changes ink's colour. The drop-in colours through roundel, whose level is the family's output policy, read from the environment:NO_COLOR,FORCE_COLOR, a pipe and--jsondecide it, and an assignment to chalk does not. SetFORCE_COLORinstead. - ink 8's behaviour, not ink 6's. Where the two majors disagree, the drop-in follows 8: Delete is not read as Backspace, and a non-interactive debug run ends with a newline.
- Smaller. With
reactandreact-reconcilerbundled, the drop-in is 0.790× ofink+react, and installs 10 packages where ink + react install 40 (Why controlroom).
Going further: the native API
controlroom/ink is the way in. A new screen can be written against open() instead, without
React: its root bundles at 0.061× of ink alone, every pane gets a static projection for pipes
and CI, --json gives an agent events, and no call waits on a key nobody can press.
Getting started is the native API's first page.
Compatibility
How controlroom/ink is graded — ink 8.0.0's own test suite, 1304 of 1304, and @inkjs/ui 2.0.0's, 102 of 102 — what is set aside and why, and the ink 6 grade that is measured but not claimed.
A full-screen dashboard
The reference dashboard: tabs, a checklist with progress, a log tail and a status line that collapses, in the alternate screen — and clean lines in a pipe and NDJSON under --json, from the same program.