chalk
Chalk styles terminal output. You chain style names and call the result as a function: chalk.blue.bold('hi') wraps the text in the right ANSI escape codes. It detects how much color the terminal supports (none, 16, 256, or 16 million), downsamples rgb and hex colors to what is available, respects FORCE_COLOR and --no-color conventions, and nests styles correctly, which naive string concatenation of escape codes gets wrong. Zero dependencies, and used as the styling layer by a huge share of Node CLI tools.
Still the default way to color terminal output, with the best API and the most correct edge-case handling; the smaller alternatives exist mostly because chalk 5 dropped CommonJS. If you can use ESM, use chalk; if you are stuck on CJS or truly size-obsessed, picocolors is fine.
Use it if
- You are building a Node CLI and want readable, composable styling (chalk.red.bold, nesting, template literals) instead of raw escape codes
- You need color support detection handled for you, including CI environments, FORCE_COLOR, --color/--no-color flags, and separate stdout/stderr detection
- You use rgb/hex brand colors and want automatic downsampling on terminals that only do 16 or 256 colors
- It is almost certainly already in your dependency tree, so using it adds nothing to install size
- You are on CommonJS and cannot migrate: chalk 5 and later are ESM only, so require('chalk') fails and the maintained line is closed to you; picocolors or ansis work from CJS
- You are writing a minimal library and count every byte: picocolors is roughly a tenth the size and covers basic colors; chalk's own author publishes yoctocolors for the same reason
- You need Node older than 22: chalk 6 requires >=22, so older runtimes pin you to chalk 5 or 4
- You need styling beyond colors, like clickable links, gradients, or template strings: those live in separate packages (terminal-link, gradient-string, chalk-template), so chalk alone will not finish the job
Setup reality
npm install chalk, import chalk from 'chalk', done: zero dependencies and types are bundled. The one real hurdle is module format: since v5 the package is pure ESM, so CommonJS projects and some Jest or older TypeScript setups fail with ERR_REQUIRE_ESM and either stay on chalk 4 or move their build to ESM. Also note that color detection means output differs by environment: piped output and CI logs get no color unless you set FORCE_COLOR, which regularly surprises people when their snapshot tests or logs look different locally versus in CI.
Patterns
Color a stringbasic-color
import chalk from 'chalk';
console.log(chalk.blue('Hello world!'));
console.log(chalk.green('ok') + ' ' + chalk.red('fail'));Pure ESM since v5: require('chalk') throws ERR_REQUIRE_ESM in CommonJS. Stay on chalk@4 or convert the project to ESM.
Combine and nest styleschain-styles
import chalk from 'chalk';
console.log(chalk.blue.bgRed.bold('Hello world!'));
console.log(chalk.red('error', chalk.underline.bgBlue('code') + '!'));
console.log(chalk.green('outer ' + chalk.blue.bold('inner') + ' outer again'));Order does not matter and later styles win conflicts, so chalk.red.yellow.green is just chalk.green. Nesting restores the outer style correctly after the inner one ends.
Style values inside template literalstemplate-literals
import chalk from 'chalk';
console.log(`
CPU: ${chalk.red('90%')}
RAM: ${chalk.green('40%')}
DISK: ${chalk.yellow('70%')}
`);Plain template literals are the idiomatic pattern; tagged-template syntax lives in the separate chalk-template package.
Define reusable theme helpersdefine-theme
import chalk from 'chalk';
const error = chalk.bold.red;
const warning = chalk.hex('#FFA500');
console.log(error('Error!'));
console.log(warning('Warning!'));A partially applied chain is just a function you can export; this keeps color choices in one module instead of sprinkled through the codebase.
Use truecolor rgb and hex valuesrgb-hex-colors
import chalk from 'chalk';
console.log(chalk.rgb(123, 45, 67).underline('reddish'));
console.log(chalk.hex('#DEADED').bold('bold gray'));
console.log(chalk.bgHex('#FF8800')('orange background'));On terminals without truecolor these are downsampled automatically, so #FF0000 becomes bright red at level 1 rather than failing.
Create an instance with a fixed color levelforce-color-level
import { Chalk } from 'chalk';
const noColor = new Chalk({ level: 0 });
const truecolor = new Chalk({ level: 3 });
console.log(noColor.red('plain text'));Prefer a new Chalk instance over assigning chalk.level, which mutates a shared singleton for every consumer in the process. Levels outside 0-3 throw.
Style stderr with its own detectionstderr-output
import { chalkStderr } from 'chalk';
console.error(chalkStderr.red('failed to connect'));stdout and stderr can have different color support (one piped, one a TTY); chalkStderr detects against stderr so redirected output stays clean.
Check what the terminal supportsdetect-color-support
import chalk, { supportsColor } from 'chalk';
if (supportsColor) {
console.log('level', chalk.level); // 1, 2, or 3
}
if (supportsColor && supportsColor.has16m) {
console.log('truecolor available');
}Users can override detection with FORCE_COLOR=0..3 or --color/--no-color; your code should trust the detected level rather than re-checking isTTY.
Get color in CI and piped outputci-force-color
# color is disabled when output is piped or in most CI
FORCE_COLOR=3 node cli.js | less -R
# explicitly disable for clean logs
FORCE_COLOR=0 node cli.js > build.logThis is the top chalk confusion: no code change makes piped output colored, only the environment variable or --color flag, by design.
Validate user-supplied style namesvalidate-style-names
import chalk, { modifierNames, foregroundColorNames } from 'chalk';
function paint(styleName, text) {
if (!foregroundColorNames.includes(styleName)) {
throw new Error(`unknown color: ${styleName}`);
}
return chalk[styleName](text);
}The exported name arrays (modifierNames, foregroundColorNames, backgroundColorNames, colorNames) exist exactly for wrappers that accept style strings from config.
Color the underline separately from the textunderline-colors
import chalk from 'chalk';
console.log(chalk.underlineRed.underlineCurly('typo'));
console.log(chalk.underlineRgb(15, 100, 204).underline('linked'));New in chalk 6, and only visible when an underline modifier is also applied; terminal support for curly or colored underlines is still spotty.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| picocolors | npm | You want the smallest practical color package and only need the standard 16 colors, from ESM or CJS. |
| ansis | npm | You want a chalk-compatible chained API plus truecolor in a smaller dual ESM/CJS package. |
| kleur | npm | You want a tiny no-dependency API of chained method calls and can skip rgb/hex support. |