.cursor/skills/ink-tui/references/TERMINAL-COMPAT.md
How to detect terminal capabilities and degrade gracefully across environments.
const isTTY = process.stdin.isTTY && process.stdout.isTTY;
const isCI = Boolean(process.env.CI);
const isPiped = !process.stdin.isTTY;
If not a TTY (piped input, CI, etc.), skip the full Ink TUI and fall back to non-interactive mode with defaults or flag-based configuration.
import { useStdout } from 'ink';
const { stdout } = useStdout();
const columns = stdout.columns; // width in characters
const rows = stdout.rows; // height in lines
Listen for resize:
useEffect(() => {
const onResize = () => { /* re-read stdout.columns/rows */ };
stdout.on('resize', onResize);
return () => stdout.off('resize', onResize);
}, []);
Ink uses chalk internally. Colors outside the terminal's gamut are automatically coerced to the closest available value.
Respect user preferences:
// NO_COLOR standard: https://no-color.org/
const noColor = 'NO_COLOR' in process.env;
// FORCE_COLOR forces color even in non-TTY contexts
const forceColor = 'FORCE_COLOR' in process.env;
Use the figures package for cross-platform symbols:
npm install figures
import figures from 'figures';
// figures.tick → ✓ (or √ on Windows CMD)
// figures.cross → ✗ (or × on Windows CMD)
// figures.bullet → ● (or * on Windows CMD)
// figures.pointer → ❯ (or > on Windows CMD)
// figures.arrowRight → → (or > on Windows CMD)
// figures.line → ─ (or - on Windows CMD)
For custom detection:
const supportsUnicode = process.env.TERM !== 'dumb'
&& !process.env.CI
&& process.platform !== 'win32';
Adapt to terminal width:
const { stdout } = useStdout();
const isNarrow = stdout.columns < 60;
const isShort = stdout.rows < 20;
return (
<Box flexDirection="column">
<Box gap={isNarrow ? 0 : 1}>
{TABS.map((tab, i) => (
<Text key={tab}>
{isNarrow ? tab[0] : tab}
</Text>
))}
</Box>
<Box borderStyle={isNarrow ? undefined : 'round'}>
{children}
</Box>
</Box>
);
Test on all of these before shipping:
Common issues:
wrap="truncate" on Text.figures package handles common fallbacks, but custom border
characters may need manual ASCII alternatives.When the terminal doesn't support interactive mode:
// cli.tsx
import { render } from 'ink';
if (!process.stdin.isTTY || process.env.CI) {
// Non-interactive mode: use flags or defaults
await runNonInteractive(parsedArgs);
} else {
// Full TUI mode
const { waitUntilExit } = render(<App />);
await waitUntilExit();
}
Or use @inquirer/prompts as a simpler fallback:
import { select, confirm } from '@inquirer/prompts';
const framework = await select({
message: 'Select your framework',
choices: [
{ name: 'Next.js', value: 'nextjs' },
{ name: 'React', value: 'react' },
],
});