cheat sheet
Chalk
Apply ANSI colours, backgrounds, and text styles to terminal output in Node.js using Chalk's chainable API, plus comparisons with picocolors and patterns for building polished CLI UX.
Chalk — Terminal string styling for Node
What it is
Chalk is the de-facto Node.js library for adding ANSI colours and styles to strings printed to a terminal. It is maintained by Sindre Sorhus and the Chalk community, written in JavaScript, and powers countless CLI tools across the npm ecosystem. Reach for it when you want richer terminal output than console.log("error: ...") — coloured log levels, dim secondary text, bold headers, hex-coloured branding. Alternatives in the same niche: picocolors (smaller, ~14× lighter), kleur, ansi-colors, and colorette — all expose a roughly similar API. Chalk pairs naturally with ora (spinners), @inquirer/prompts (interactive prompts), and commander (CLI argument parsing).
Install
Chalk 5+ is ESM-only — you must import it, not require(). If your project is still CommonJS (no "type": "module" in package.json), install Chalk 4 instead.
# Modern ESM project
npm install chalk
# CommonJS project — pin to v4
npm install chalk@4
# Or with other package managers
pnpm add chalk
yarn add chalk
bun add chalk
Output: (none — exits 0 on success)
Syntax
Chalk exposes a default object with chainable style methods. Each method returns a new chainable object you can call as a tagged-template or as a function to wrap a string.
import chalk from "chalk";
chalk.<color>(text);
chalk.<color>.<style>(text);
chalk.<style>.<color>.<bg>(text);
chalk`{red text}`; // template tag
Output: (none — exits 0 on success)
Essential styles
| Category | Methods |
|---|---|
| Foreground basic | black, red, green, yellow, blue, magenta, cyan, white, gray (alias grey) |
| Foreground bright | redBright, greenBright, yellowBright, blueBright, magentaBright, cyanBright, whiteBright |
| Background basic | bgBlack, bgRed, bgGreen, bgYellow, bgBlue, bgMagenta, bgCyan, bgWhite |
| Background bright | bgRedBright, bgGreenBright, … |
| Text modifiers | bold, dim, italic, underline, inverse, hidden, strikethrough, reset, visible |
| Arbitrary colour | .rgb(r, g, b), .hex("#rrggbb"), .bgRgb(...), .bgHex(...) |
| 256-colour | .ansi256(n), .bgAnsi256(n) |
Basic colouring
Call any colour method on chalk with a string and it returns the string wrapped in ANSI escape codes. console.log then renders it in colour in any terminal that supports ANSI (every macOS/Linux terminal, Windows Terminal, VS Code's integrated terminal).
import chalk from "chalk";
console.log(chalk.red("Error: file not found"));
console.log(chalk.green("Success: built in 1.2s"));
console.log(chalk.yellow("Warning: deprecated API"));
console.log(chalk.blue("Info: 42 files processed"));
console.log(chalk.gray("debug: cache hit"));
Output:
Error: file not found ← in red
Success: built in 1.2s ← in green
Warning: deprecated API ← in yellow
Info: 42 files processed ← in blue
debug: cache hit ← in gray
Chaining styles
Style methods are chainable: chalk.red.bgWhite.bold("text") applies all three to the string. Order does not matter; the last colour for a given category (foreground/background) wins.
import chalk from "chalk";
console.log(chalk.red.bold("CRITICAL ERROR"));
console.log(chalk.bgYellow.black.bold(" WARN "));
console.log(chalk.cyan.underline("https://example.com"));
console.log(chalk.dim.italic("(optional metadata)"));
console.log(chalk.red.strikethrough("v1 — deprecated"));
// Last colour wins
console.log(chalk.red.green("I am green, not red"));
Output:
CRITICAL ERROR ← bold red
WARN ← black on yellow background, bold
https://example.com ← underlined cyan
(optional metadata) ← dim italic
v1 — deprecated ← red with line-through
I am green, not red ← green
Nested and concatenated styles
You can nest chalk calls: each inner call resets to its own colour for that substring, then the outer style resumes. This is how you mix colours in a single line — concatenate strings with + or interpolate with template literals.
import chalk from "chalk";
console.log(chalk.red("Error in " + chalk.bold("config.ts") + " on line 42"));
console.log(
`${chalk.green("✓")} ${chalk.bold("build")} ${chalk.dim("(1.2s)")}`,
);
const route = "/api/users/:id";
console.log(chalk.cyan(`GET ${chalk.underline(route)}`));
Output:
Error in config.ts on line 42 ← red with "config.ts" bold red
✓ build (1.2s) ← green check, bold "build", dim "(1.2s)"
GET /api/users/:id ← cyan with route underlined
True-colour and 256-colour
For modern terminals you can use any RGB or hex colour — useful for matching brand colours, syntax-highlighting tokens, or rendering colour swatches. Fall back to 256-colour mode on older terminals (the chalk.level API auto-detects).
import chalk from "chalk";
// True-colour (24-bit RGB)
console.log(chalk.rgb(138, 92, 255)("Jay's neon purple"));
console.log(chalk.hex("#8a5cff")("same colour, hex form"));
console.log(chalk.bgHex("#0f172a").hex("#f8fafc")("dark theme"));
// 256-colour mode (0–255)
console.log(chalk.ansi256(208)("Vivid orange (256-colour mode)"));
console.log(chalk.bgAnsi256(57).white("Indigo background"));
// Build a small palette
const palette = ["#ff5252", "#ffb74d", "#fff176", "#aed581", "#4fc3f7"];
console.log(palette.map((c) => chalk.bgHex(c).black(" " + c + " ")).join(""));
Output:
Jay's neon purple ← in #8a5cff
same colour, hex form ← same
dark theme ← #f8fafc on #0f172a
Vivid orange (256-colour mode)
Indigo background ← white on indigo
#ff5252 #ffb74d #fff176 #aed581 #4fc3f7 ← five swatches
Colour-support detection
Chalk auto-detects terminal capabilities at import time and stores the result in chalk.level: 0 = no colour (output is piped or NO_COLOR=1), 1 = 16 colours, 2 = 256 colours, 3 = truecolour. You can force a level if you need to. The deeper detection logic lives in the supports-color package (a Chalk sibling).
import chalk, { supportsColor } from "chalk";
console.log("Current level:", chalk.level);
console.log("Supports 256-colour:", supportsColor && supportsColor.has256);
console.log("Supports truecolour:", supportsColor && supportsColor.has16m);
// Force on/off (rarely needed — usually env vars are the right answer)
chalk.level = 0;
console.log(chalk.red("still red?")); // no — emitted as plain text
chalk.level = 3;
console.log(chalk.hex("#8a5cff")("neon!"));
Output:
Current level: 3
Supports 256-colour: true
Supports truecolour: true
still red?
neon! ← in #8a5cff
Disabling colour with env vars
| Variable | Effect |
|---|---|
NO_COLOR=1 | Disable all colour (cross-tool standard) |
FORCE_COLOR=0 | Disable |
FORCE_COLOR=1 | Enable 16-colour |
FORCE_COLOR=2 | Enable 256-colour |
FORCE_COLOR=3 | Enable truecolour |
NO_COLOR=1 node script.js # no ANSI codes in output
FORCE_COLOR=3 node script.js # force truecolour even when piped
Output: (none — exits 0 on success)
ESM-only — and the v4 fallback
Chalk v5 dropped CommonJS support. If you require("chalk") you get:
Error [ERR_REQUIRE_ESM]: require() of ES Module chalk is not supported.
Fix: convert your project to ESM (set "type": "module" in package.json and use import) — or pin to chalk@4, which is still maintained for CJS users.
// package.json — ESM
{
"type": "module",
"dependencies": {
"chalk": "^5.3.0"
}
}
// ESM
import chalk from "chalk";
console.log(chalk.green("ok"));
// CJS — requires chalk@4
const chalk = require("chalk");
console.log(chalk.green("ok"));
Output:
ok ← in green (both cases)
Library comparison
| Library | Size (gzipped) | API | Notes |
|---|---|---|---|
| chalk v5 | ~3 kB | Chainable: chalk.red.bold(s) | ESM only; richest feature set |
| picocolors | ~0.4 kB | Functional: pc.red(s) | Drop-in for most use cases; 14× lighter |
| kleur | ~2 kB | Chainable: kleur.red().bold(s) | Functions, not getters; very fast |
| ansi-colors | ~1 kB | Chainable: c.red.bold(s) | Drop-in for chalk@1–4 API |
| colorette | ~1 kB | Functional: c.red(s) | Used internally by Vite, Rollup |
Rule of thumb: for a library or shared dependency, prefer picocolors to keep your install footprint small. For an application or CLI you control, Chalk's richer API (hex, rgb, template literals, ansi256) is worth the extra kilobytes.
// picocolors — almost identical surface
import pc from "picocolors";
console.log(pc.red("Error"));
console.log(pc.green(pc.bold("OK")));
Output:
Error ← red
OK ← bold green
Pairing with ora (spinners)
Ora is the canonical spinner library and uses Chalk colours by default. A spinner is just a frame-by-frame animation rendered between .start() and .succeed() / .fail() calls.
npm install ora chalk
Output: (none — exits 0 on success)
import ora from "ora";
import chalk from "chalk";
const spinner = ora({
text: "Fetching from " + chalk.underline("api.example.com"),
color: "magenta",
}).start();
try {
await fetch("https://api.example.com/users").then((r) => r.json());
spinner.succeed(chalk.green("Fetched 142 users"));
} catch (err) {
spinner.fail(chalk.red(`Failed: ${(err as Error).message}`));
process.exit(1);
}
Output: (animated, then final line)
⠋ Fetching from api.example.com
✔ Fetched 142 users ← in green
Pairing with @inquirer/prompts
@inquirer/prompts provides interactive prompts (input, select, confirm, password). Chalk is the default theme engine.
npm install @inquirer/prompts chalk
Output: (none — exits 0 on success)
import { select, input, confirm } from "@inquirer/prompts";
import chalk from "chalk";
const name = await input({
message: chalk.bold("Project name:"),
default: "my-app",
});
const framework = await select({
message: chalk.bold("Pick a framework:"),
choices: [
{ name: chalk.cyan("React"), value: "react" },
{ name: chalk.green("Vue"), value: "vue" },
{ name: chalk.red("Svelte"), value: "svelte" },
],
});
const ok = await confirm({
message: `Create ${chalk.magenta(name)} (${framework})?`,
default: true,
});
console.log(ok ? chalk.green("✓ Scaffolding…") : chalk.gray("Aborted"));
Output:
? Project name: my-app
? Pick a framework: React
? Create my-app (react)? Yes
✓ Scaffolding…
Common pitfalls
- ESM/CJS mismatch —
require("chalk")on v5 throwsERR_REQUIRE_ESM. Either set"type": "module"and useimport, or pin tochalk@4. - Piped output is colourless —
node script.js > out.txtstrips ANSI by design (Chalk'sleveldrops to 0). Force withFORCE_COLOR=1when you actually want codes preserved. - CI strips colour — most CI runners set
CI=true, which Chalk respects (no colour). SetFORCE_COLOR=2in your workflow when you want coloured logs. - Hex colours in 16-colour terminals —
chalk.hex("#8a5cff")is downgraded to the nearest 16-colour approximation (often muddy). Detect withchalk.level === 3for accuracy. - Counting characters with ANSI codes —
"red text".lengthis 8;chalk.red("red text").lengthis 18 (includes escape codes). Strip codes with thestrip-ansipackage before measuring. chalk.level = 0is global — setting it disables colour for the whole process. Use a sub-instance vianew Chalk({ level: ... })if you need per-call control.- Background colour without space —
chalk.bgRed("X")only colours the single character; pad with spaces for a visible "tag" (chalk.bgRed(" X ")). - Forgetting NO_COLOR —
NO_COLORis a cross-tool standard (https://no-color.org/); honour it. Chalk already does — do not override it in your CLI. - Hard-coded ANSI codes elsewhere — mixing
"[31m"with Chalk produces broken nesting. Pick one approach per code path. chalk@5import shape —import { red } from "chalk"does not work; the named exports differ. Useimport chalk from "chalk"; chalk.red(...).
Real-world recipes
Coloured log levels
A tiny logger that prefixes every line with a styled level tag and a timestamp. The kind of thing that ships in every CLI.
import chalk from "chalk";
type Level = "info" | "warn" | "error" | "debug";
const TAGS: Record<Level, string> = {
info: chalk.bgBlue.black(" INFO "),
warn: chalk.bgYellow.black(" WARN "),
error: chalk.bgRed.white(" ERROR "),
debug: chalk.bgGray.white(" DEBUG "),
};
function log(level: Level, msg: string) {
const ts = chalk.dim(new Date().toISOString().slice(11, 19));
console.log(`${ts} ${TAGS[level]} ${msg}`);
}
log("info", "Server listening on port 3000");
log("warn", "Cache miss for key=" + chalk.cyan("user:42"));
log("error", "Connection refused: " + chalk.underline("db.example.com:5432"));
log("debug", "Heap usage: 42 MB");
Output:
14:30:45 INFO Server listening on port 3000
14:30:45 WARN Cache miss for key=user:42
14:30:45 ERROR Connection refused: db.example.com:5432
14:30:45 DEBUG Heap usage: 42 MB
Diff-style output (added/removed lines)
Render a tiny text diff with green/red lines, mimicking git diff output. Useful for migration tools that show the user what would change.
import chalk from "chalk";
const before = ["foo", "bar", "baz"];
const after = ["foo", "qux", "baz", "new"];
const beforeSet = new Set(before);
const afterSet = new Set(after);
console.log(chalk.bold("--- before"));
console.log(chalk.bold("+++ after"));
for (const line of before) {
if (!afterSet.has(line)) console.log(chalk.red("- " + line));
}
for (const line of after) {
if (!beforeSet.has(line)) console.log(chalk.green("+ " + line));
}
for (const line of before) {
if (afterSet.has(line)) console.log(chalk.gray(" " + line));
}
Output:
--- before
+++ after
- bar ← red
+ qux ← green
+ new ← green
foo ← gray
baz ← gray
Progress-reporting CLI with colour-coded stages
A multi-stage CLI that shows progress with coloured prefixes and a final summary. Pairs Chalk with ora and Node's built-in performance.now().
import chalk from "chalk";
import ora from "ora";
import { performance } from "node:perf_hooks";
type Step = { name: string; run: () => Promise<void> };
async function runPipeline(steps: Step[]) {
const start = performance.now();
for (const [i, step] of steps.entries()) {
const prefix = chalk.dim(`[${i + 1}/${steps.length}]`);
const spinner = ora(`${prefix} ${step.name}`).start();
const t0 = performance.now();
try {
await step.run();
const ms = (performance.now() - t0).toFixed(0);
spinner.succeed(`${prefix} ${step.name} ${chalk.dim(`(${ms}ms)`)}`);
} catch (err) {
spinner.fail(`${prefix} ${chalk.red(step.name)} — ${(err as Error).message}`);
process.exit(1);
}
}
const total = ((performance.now() - start) / 1000).toFixed(2);
console.log("\n" + chalk.green.bold("✓ All steps passed ") + chalk.dim(`(${total}s)`));
}
await runPipeline([
{ name: "Lint", run: () => new Promise((r) => setTimeout(r, 300)) },
{ name: "Typecheck", run: () => new Promise((r) => setTimeout(r, 500)) },
{ name: "Test", run: () => new Promise((r) => setTimeout(r, 700)) },
{ name: "Build", run: () => new Promise((r) => setTimeout(r, 400)) },
]);
Output:
✔ [1/4] Lint (302ms)
✔ [2/4] Typecheck (503ms)
✔ [3/4] Test (706ms)
✔ [4/4] Build (405ms)
✓ All steps passed (1.92s)
Branded banner for a CLI's --help
Bigger CLIs print a branded banner. Combine chalk.hex(...) for brand colour with a hand-drawn ASCII logo.
import chalk from "chalk";
const BRAND = "#8a5cff";
const banner = `
${chalk.hex(BRAND).bold(" ███╗ ███╗ █████╗ ██╗ ██╗███████╗")}
${chalk.hex(BRAND).bold(" ████╗ ████║██╔══██╗██║ ██╔╝██╔════╝")}
${chalk.hex(BRAND).bold(" ██╔████╔██║███████║█████╔╝ █████╗ ")}
${chalk.hex(BRAND).bold(" ██║╚██╔╝██║██╔══██║██╔═██╗ ██╔══╝ ")}
${chalk.hex(BRAND).bold(" ██║ ╚═╝ ██║██║ ██║██║ ██╗███████╗")}
${chalk.bold("make")} ${chalk.dim("— scaffold a new project")}
${chalk.dim("Usage:")} ${chalk.cyan("make <template> [options]")}
${chalk.dim("Examples:")}
${chalk.cyan("make react my-app")} ${chalk.dim("React + Vite + TS")}
${chalk.cyan("make api my-service")} ${chalk.dim("Fastify + Zod")}
`;
console.log(banner);
Output:
███╗ ███╗ █████╗ ██╗ ██╗███████╗ ← in #8a5cff, bold
████╗ ████║██╔══██╗██║ ██╔╝██╔════╝
██╔████╔██║███████║█████╔╝ █████╗
██║╚██╔╝██║██╔══██║██╔═██╗ ██╔══╝
██║ ╚═╝ ██║██║ ██║██║ ██╗███████╗
make — scaffold a new project
Usage: make <template> [options]
Examples:
make react my-app React + Vite + TS
make api my-service Fastify + Zod
Honouring NO_COLOR in a custom theme
When you wrap Chalk in a theme helper, make sure the wrapper degrades gracefully when colour is off. Reading chalk.level === 0 is the canonical check.
import chalk from "chalk";
const theme = {
primary: chalk.level === 0 ? (s: string) => s : chalk.hex("#8a5cff"),
ok: chalk.level === 0 ? (s: string) => s : chalk.green,
warn: chalk.level === 0 ? (s: string) => s : chalk.yellow,
err: chalk.level === 0 ? (s: string) => s : chalk.red.bold,
dim: chalk.level === 0 ? (s: string) => s : chalk.dim,
};
console.log(theme.primary("Welcome to jockey"));
console.log(theme.ok("✓ ready"));
console.log(theme.err("✗ failed: connection refused"));
console.log(theme.dim("(set NO_COLOR=1 to disable colour)"));
Output: (with NO_COLOR=1)
Welcome to jockey
✓ ready
✗ failed: connection refused
(set NO_COLOR=1 to disable colour)