cli-color
cli-color is a CommonJS terminal formatting toolkit built around chainable ANSI style functions. It covers the basic and bright 16-color palettes, 256-color xterm codes, backgrounds, bold and other text modes, nested styles, ANSI-aware slicing and stripping, aligned columns, cursor movement, erasing, screen size, simple character art, and an interval-driven throbber. It returns strings and control sequences; your application decides when and where to write them.
cli-color remains useful when an existing CLI relies on its unusual combination of styling, tables, slicing, and cursor controls. For a new CLI that only needs color, picocolors, Chalk, or Kleur has a smaller dependency and packaging surface; add separate width and terminal-control tools only when required.
Use it if
- You maintain a CommonJS CLI that already uses cli-color's chainable styles and terminal-control helpers
- You want styling, ANSI-aware slicing, columns, cursor motion, screen erasing, and a throbber from one established package
- You still support very old Node runtimes and accept the dependency stack needed to preserve that compatibility
- Your output is trusted, mostly ASCII text and you explicitly disable control sequences for pipes, logs, and unsupported terminals
- You want accurate capability detection: color output is enabled whenever NO_COLOR is absent, without checking process.stdout.isTTY, TERM, color depth, CI conventions, or a FORCE_COLOR policy
- You want a small modern styling dependency: cli-color has five direct runtime dependencies for ES5-era descriptors, iterators, memoization, and timers, while picocolors and several peers are dependency-free
- You need ESM exports or TypeScript declarations: 2.0.4 ships CommonJS subpaths and no types
- You need correct terminal-cell widths for multilingual output: columns, getStrippedLength, and slice count JavaScript UTF-16 code units after removing ANSI, so wide CJK characters, emoji, surrogate pairs, and combining marks misalign or split
- You need truecolor RGB or browser styling: the API stops at basic, bright, and 256-color xterm palettes and imports Node process and terminal-specific behavior
Setup reality
npm install cli-color adds a CommonJS package with no native build, peer dependency, credentials, or configuration file. It declares Node >=0.10, which explains its five direct compatibility-oriented dependencies and old module shape. require('cli-color') eagerly exposes styling plus columns, movement, erasing, slicing, length, art, throbber, reset, and window-size helpers; individual subpaths such as cli-color/strip also work because there is no exports map. A style is a callable object, and chaining properties builds a memoized formatter. Multiple arguments are joined with one space. Nested styles are repaired by reopening outer styles after inner closing codes. The only automatic color policy is NO_COLOR, checked each time a formatter runs. There is no TTY detection, so redirected output and CI logs receive ANSI by default unless your program sets NO_COLOR or chooses plain output itself. Conversely, terminals that support color cannot be forced through a documented public option when NO_COLOR is inherited incorrectly. xterm codes are clamped to 0 through 255 and mapped down to basic colors on Windows rather than negotiated with the terminal. Cursor, erase, beep, and reset values are raw control sequences; reset clears the screen and moves the cursor, not merely text styling. Guard interactive operations with process.stdout.isTTY and never send them into machine-readable output. Styling does not sanitize input, so untrusted ESC, BEL, carriage-return, backspace, and other controls can still alter a terminal. ANSI-aware length and slice helpers understand escape codes but not display-cell width or grapheme clusters. The columns helper inherits that limitation. The throbber owns a setInterval and backspaces over one character; call stop on success, failure, and shutdown or the timer can keep the process alive and corrupt later output. Version 2.0.4 was a 2024 dependency and license maintenance release, with no functional release since the 2022 cursor-line fix.
Patterns
Predefine reusable status stylesdefine-status-styles
const clc = require('cli-color');
const error = clc.red.bold;
const warn = clc.yellow;
const success = clc.green;
console.error(error('Error:'), 'request failed');
console.warn(warn('Warning:'), 'using defaults');
console.log(success('Done'));Predefining styles reuses the package's memoized formatter objects. Multiple arguments passed to one formatter are joined with spaces.
Nest one style inside anothernest-styles
const message = clc.red(
'failed ' + clc.bold.white('authentication') + ' check'
);
console.error(message);cli-color detects its own ANSI closes and reopens the outer style after the inner formatter, so the final text returns to red.
Honor noninteractive output explicitlydisable-color-for-pipes
if (!process.stdout.isTTY) {
process.env.NO_COLOR = '1';
}
const line = clc.cyan('build') + ' complete';
process.stdout.write(line + '\n');cli-color checks NO_COLOR at each formatting call but does not check isTTY itself. Set policy before constructing output that may go to files, pipes, or log collectors.
Use a 256-color foreground and backgrounduse-xterm-color
const badge = clc.xterm(202).bgXterm(236).bold;
console.log(badge(' DEPLOY '));Codes are clamped to 0 through 255. On Windows, the package maps xterm requests to nearby basic colors rather than detecting true 256-color capability.
Strip terminal formatting for a log sinkstrip-ansi
const formatted = clc.red.bold('failed');
const plain = clc.strip(formatted);
fileLogger.write(plain);strip removes ANSI sequences matched by the package's built-in regex. It does not sanitize every possible terminal control character in arbitrary input.
Measure text without ANSI bytesmeasure-styled-text
const label = clc.bold('Status') + ': ' + clc.green('ready');
const length = clc.getStrippedLength(label);
console.log(length);The result is JavaScript string length after stripping ANSI, not terminal column width. Emoji, CJK text, combining marks, and tabs can occupy a different number of cells.
Slice text while preserving active stylesslice-styled-text
const value = clc.bold('foo') + 'bar' + clc.red('hello');
const excerpt = clc.slice(value, 1, 7);
process.stdout.write(excerpt + '\n');slice reopens and closes recognized styles around the selected visible range. Indices are UTF-16 code units, so avoid slicing through emoji or combining sequences.
Render an ANSI-aware tablerender-columns
const table = clc.columns(
[
[clc.bold('Package'), clc.bold('Downloads')],
['alpha', 1200],
['beta', 42],
],
{
sep: ' ',
columns: [{ align: 'left' }, { align: 'right' }],
}
);
process.stdout.write(table);columns supports multiline cells and ignores ANSI bytes for padding, but it still misaligns wide and combining Unicode characters because it uses stripped string length.
Rewrite one interactive status lineupdate-status-line
function renderStatus(text) {
if (!process.stdout.isTTY) {
process.stdout.write(text + '\n');
return;
}
process.stdout.write(
clc.move.lineBegin + clc.erase.lineRight + clc.cyan(text)
);
}
renderStatus('Uploading 40%');Cursor and erase sequences should go only to an interactive terminal. Add a final newline when the operation finishes so the shell prompt starts cleanly.
Move to a zero-based terminal positionmove-cursor
if (process.stdout.isTTY) {
process.stdout.write(clc.move.to(0, 0));
process.stdout.write(clc.bold('Top-left'));
}move.to accepts zero-based x and y, then emits one-based terminal coordinates. The related clc.reset clears the entire screen before moving home.
Remove control characters from untrusted labelssanitize-terminal-input
function terminalText(value) {
return String(value).replace(
/[\u0000-\u0008\u000B-\u001F\u007F-\u009F]/gu,
''
);
}
console.log(clc.yellow(terminalText(userSuppliedLabel)));Styling does not neutralize embedded ESC, BEL, carriage return, or backspace. Choose an allowlist or stricter policy when displaying untrusted data in a terminal.
Stop a throbber on every exit pathrun-throbber
const spinner = clc.throbber(
(chunk) => process.stdout.write(chunk),
120,
clc.cyan
);
spinner.start();
try {
await runTask();
} finally {
spinner.stop();
process.stdout.write('\n');
}The throbber uses setInterval and backspace. stop clears the timer and erases its current character; without it, the process may stay alive and later output can be overwritten.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| picocolors | npm | Use a tiny dependency-free color formatter when cursor control, tables, and ANSI-aware slicing are unnecessary |
| chalk | npm | Use the mainstream ESM styling API when modern color-level detection, truecolor, and TypeScript support matter |
| kleur | npm | Use a small chainable formatter with simple enabled or disabled control and no runtime dependencies |
| ansi-colors | npm | Use a dependency-free CommonJS styling alternative for an older CLI that does not need cli-color's layout helpers |