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.

bash
# 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.

typescript
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

CategoryMethods
Foreground basicblack, red, green, yellow, blue, magenta, cyan, white, gray (alias grey)
Foreground brightredBright, greenBright, yellowBright, blueBright, magentaBright, cyanBright, whiteBright
Background basicbgBlack, bgRed, bgGreen, bgYellow, bgBlue, bgMagenta, bgCyan, bgWhite
Background brightbgRedBright, bgGreenBright, …
Text modifiersbold, 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).

typescript
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:

text
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.

typescript
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:

text
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.

typescript
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:

text
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).

typescript
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:

text
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).

typescript
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:

text
Current level: 3
Supports 256-colour: true
Supports truecolour: true
still red?
neon!                           ← in #8a5cff

Disabling colour with env vars

VariableEffect
NO_COLOR=1Disable all colour (cross-tool standard)
FORCE_COLOR=0Disable
FORCE_COLOR=1Enable 16-colour
FORCE_COLOR=2Enable 256-colour
FORCE_COLOR=3Enable truecolour
bash
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:

text
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.

json
// package.json — ESM
{
  "type": "module",
  "dependencies": {
    "chalk": "^5.3.0"
  }
}
typescript
// ESM
import chalk from "chalk";
console.log(chalk.green("ok"));
javascript
// CJS — requires chalk@4
const chalk = require("chalk");
console.log(chalk.green("ok"));

Output:

text
ok                              ← in green (both cases)

Library comparison

LibrarySize (gzipped)APINotes
chalk v5~3 kBChainable: chalk.red.bold(s)ESM only; richest feature set
picocolors~0.4 kBFunctional: pc.red(s)Drop-in for most use cases; 14× lighter
kleur~2 kBChainable: kleur.red().bold(s)Functions, not getters; very fast
ansi-colors~1 kBChainable: c.red.bold(s)Drop-in for chalk@1–4 API
colorette~1 kBFunctional: 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.

typescript
// picocolors — almost identical surface
import pc from "picocolors";

console.log(pc.red("Error"));
console.log(pc.green(pc.bold("OK")));

Output:

text
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.

bash
npm install ora chalk

Output: (none — exits 0 on success)

typescript
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)

text
⠋ 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.

bash
npm install @inquirer/prompts chalk

Output: (none — exits 0 on success)

typescript
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:

text
? Project name: my-app
? Pick a framework: React
? Create my-app (react)? Yes
✓ Scaffolding…

Common pitfalls

  1. ESM/CJS mismatchrequire("chalk") on v5 throws ERR_REQUIRE_ESM. Either set "type": "module" and use import, or pin to chalk@4.
  2. Piped output is colourlessnode script.js > out.txt strips ANSI by design (Chalk's level drops to 0). Force with FORCE_COLOR=1 when you actually want codes preserved.
  3. CI strips colour — most CI runners set CI=true, which Chalk respects (no colour). Set FORCE_COLOR=2 in your workflow when you want coloured logs.
  4. Hex colours in 16-colour terminalschalk.hex("#8a5cff") is downgraded to the nearest 16-colour approximation (often muddy). Detect with chalk.level === 3 for accuracy.
  5. Counting characters with ANSI codes"red text".length is 8; chalk.red("red text").length is 18 (includes escape codes). Strip codes with the strip-ansi package before measuring.
  6. chalk.level = 0 is global — setting it disables colour for the whole process. Use a sub-instance via new Chalk({ level: ... }) if you need per-call control.
  7. Background colour without spacechalk.bgRed("X") only colours the single character; pad with spaces for a visible "tag" (chalk.bgRed(" X ")).
  8. Forgetting NO_COLORNO_COLOR is a cross-tool standard (https://no-color.org/); honour it. Chalk already does — do not override it in your CLI.
  9. Hard-coded ANSI codes elsewhere — mixing "" with Chalk produces broken nesting. Pick one approach per code path.
  10. chalk@5 import shapeimport { red } from "chalk" does not work; the named exports differ. Use import 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.

typescript
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:

text
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.

typescript
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:

text
--- 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().

typescript
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:

text
✔ [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.

typescript
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:

text
   ███╗   ███╗ █████╗ ██╗  ██╗███████╗     ← 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.

typescript
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)

text
Welcome to jockey
✓ ready
✗ failed: connection refused
(set NO_COLOR=1 to disable colour)