cheat sheet
chalk
Package-level reference for the chalk ANSI-styling library on npm — install, ESM-only policy, peer-deps, and alternatives.
chalk
What it is
chalk is a Node.js library for applying ANSI colours, backgrounds, and text styles to terminal output via a chainable API. It is the de-facto styling layer pulled in by most Node CLIs (npm itself, eslint, jest, prettier, and thousands more) and is maintained by Sindre Sorhus.
Reach for chalk when you want the broadest API surface (256-colour, truecolor, nested chains, template strings). Reach for picocolors or kleur when you want a smaller, faster, CJS-friendly dependency without losing basic colour support.
Install
Install via your preferred package manager. The library has no CLI of its own — it is a programmatic dependency.
npm install chalk
Output: added chalk to dependencies
pnpm add chalk
Output: added 1 package, linked from store
yarn add chalk
Output: added chalk
bun add chalk
Output: installed chalk
deno add npm:chalk
Output: added npm:chalk to import map
Versioning & Node support
The current major line is 5.x, which is pure ESM — require("chalk") no longer works. Stay on 4.x (CJS/ESM dual) if your project still publishes CommonJS and cannot adopt ESM.
chalk@5— Node 14+ (effectively 16+ in practice). ESM-only. No default export —import chalk from "chalk"works because the default IS the chalk instance.chalk@4— Node 10+. Dual ESM/CJS via conditional exports. Still receives security patches but no new features.- Semver is honoured — major bumps signal breaking changes (the 4 → 5 ESM cutover being the most recent).
Package metadata
- Maintainer: Sindre Sorhus (
sindresorhuson GitHub) and contributors - Project home: github.com/chalk/chalk
- Docs: README on GitHub and npm
- npm: npmjs.com/package/chalk
- License: MIT
- First released: 2013
- Downloads: consistently in the top-5 most-downloaded npm packages — well over 200 million weekly downloads via transitive dependencies.
Peer dependencies & extras
chalk has zero runtime dependencies on the 5.x line (the supports-color and ansi-styles helpers were folded inline). No peer-deps. No extras to install.
Adjacent packages from the same maintainer that you may want alongside it:
chalk-template— tagged-template-literal syntax (chalk` {red {bold hello}} `). Was bundled inchalk@4; extracted inchalk@5so the core stays minimal.supports-color— detect whether the current TTY supports colour; exposed inchalkaschalk.level, but useful standalone for non-chalk output.cli-color/ansi-colors— drop-in-ish alternatives; see below.
Alternatives
| Package | Trade-off |
|---|---|
picocolors | ~14× smaller, ~2× faster, no chaining. Used inside Node tooling (PostCSS, Vite, Prettier internals) where startup time matters. CJS + ESM. |
kleur | Chainable API similar to chalk, smaller bundle, dual CJS/ESM. Good middle ground. |
ansi-colors | Fork of chalk-4 API with no dependencies and CJS support. Used by gulp/inquirer ecosystem. |
colorette | Tiny, no chaining, auto-detect TTY support. Drop-in for picocolors with slightly different API. |
Built-in util.styleText | Node 20.12+ ships util.styleText("red", "hi"). Zero-dep but limited styles. Best for tiny scripts. |
Common gotchas
- ESM-only since v5.
const chalk = require("chalk")throwsERR_REQUIRE_ESM. Either upgrade your project to ESM ("type": "module"inpackage.json) or pin tochalk@^4. Many tutorials and Stack Overflow answers still reference the v4 CJS form. - No default-named exports.
import { red } from "chalk"does NOT work — chalk's default export is the chalk instance itself. Useimport chalk from "chalk"and thenchalk.red(...). chalk.leveldetection is unreliable in CI and Docker. Some terminals don't advertise true-colour support; checkFORCE_COLOR=3(truecolor) orFORCE_COLOR=0(off) to override detection. CI logs sometimes strip ANSI silently, leaving raw escape codes in the build log.- Piping to a non-TTY strips colour. When stdout is a pipe (
mycli | less), chalk auto-disables colour. UseFORCE_COLOR=1to force ANSI escapes through, or--color/--no-colorflags wired into your CLI. - Windows legacy consoles.
cmd.exeand older PowerShell versions render ANSI codes as garbage. Windows Terminal and PowerShell 7+ are fine. The fix on legacy consoles ischalk.level = 0. - Bundle-size in browsers. Despite the slim core, chalk is intended for Node — bundling for the browser pulls in
node:ttyshims. Preferpicocolorsor a CSS-styled console-logger for browser code.
Real-world recipes
The companion article covers themed loggers, diffs, and banners. The recipes below target package-level concerns: capability detection, fallback hierarchies, and CLI ergonomics that work outside the controlled-demo case.
Capability-aware theme with three-tier fallback
A production CLI should adapt to four terminal tiers: truecolor, 256-colour, 16-colour, and no-colour. The pattern below picks the best representation available at module-load time and freezes it for the process lifetime.
import chalk from "chalk";
type Painter = (s: string) => string;
function pick(truecolor: Painter, ansi256: Painter, ansi16: Painter): Painter {
switch (chalk.level) {
case 3: return truecolor;
case 2: return ansi256;
case 1: return ansi16;
default: return (s) => s;
}
}
export const theme = {
brand: pick(chalk.hex("#8a5cff"), chalk.ansi256(99), chalk.magenta),
ok: pick(chalk.hex("#22c55e"), chalk.ansi256(40), chalk.green),
warn: pick(chalk.hex("#f59e0b"), chalk.ansi256(208), chalk.yellow),
err: pick(chalk.hex("#ef4444"), chalk.ansi256(196), chalk.red),
muted: chalk.dim,
};
Output: all paint functions defined; theme.brand("hi") works at any colour level.
--color / --no-color flag wiring
Most CLIs let the user override colour detection. The pattern: parse argv before any styled output, then set chalk.level accordingly.
import chalk from "chalk";
const argv = process.argv.slice(2);
if (argv.includes("--no-color") || process.env.NO_COLOR) {
chalk.level = 0;
} else if (argv.includes("--color")) {
chalk.level = chalk.level || 1;
} else if (argv.includes("--color=always")) {
chalk.level = 3;
}
console.log(chalk.green("ready"));
Output: honours both env and CLI flags.
Per-instance Chalk with new Chalk()
When you need different colour levels in the same process (a CLI that writes coloured text to a TTY but plain text to a log file), construct sub-instances rather than mutating the global chalk.level.
import { Chalk } from "chalk";
const tty = new Chalk({ level: chalk.level });
const log = new Chalk({ level: 0 });
process.stdout.write(tty.green("OK") + "\n");
fs.appendFileSync("run.log", log.green("OK") + "\n");
Output: terminal shows colour; log file stays plain.
Tagged-template syntax via chalk-template
The template-tag form was extracted from chalk@4. Install separately and you regain t`{red {bold hi}} ` markup.
import t from "chalk-template";
console.log(t`Build {green succeeded} in {bold 1.2s}`);
console.log(t`{red Error} on {underline ./src/index.ts}`);
Output: styled text with nested markup parsed at runtime.
Inline hyperlinks (terminal-link)
For terminals that support OSC 8 hyperlinks (iTerm2, Kitty, Wezterm, modern GNOME Terminal), pair chalk with terminal-link to produce clickable text.
import chalk from "chalk";
import terminalLink from "terminal-link";
const url = terminalLink(chalk.underline.cyan("docs"), "https://example.com/docs");
console.log(`See ${url} for details.`);
Output: clickable underlined cyan "docs" link; falls back to plain URL on unsupported terminals.
Testing & CI integration
CLI output assertions are the bulk of CLI testing. Two patterns dominate: snapshot tests against the styled output, and assertion against the stripped output.
Strip-ANSI before asserting
The most stable approach — your tests assert on content, not codes, and remain green even when chalk's encoding changes minor.
import { describe, it, expect, vi } from "vitest";
import stripAnsi from "strip-ansi";
import chalk from "chalk";
describe("logger", () => {
it("prefixes warnings", () => {
const log = vi.spyOn(console, "log").mockImplementation(() => {});
console.log(chalk.yellow("WARN") + " cache miss");
const written = stripAnsi((log.mock.calls[0] as string[])[0]);
expect(written).toBe("WARN cache miss");
});
});
Output: 1 passed.
Force colour in CI for log readability
GitHub Actions, GitLab CI, and CircleCI all support ANSI in log viewers. Force colour explicitly because chalk auto-detects CI=true and disables.
# .github/workflows/test.yml
env:
FORCE_COLOR: 2
CI: true
Output: test runner output appears coloured in the GitHub Actions log viewer.
Snapshot tests against styled output
Cleaner-looking but flakier — chalk version bumps or terminal-capability changes break snapshots. Pin chalk.level deterministically before snapshotting.
import { test, expect } from "vitest";
import chalk from "chalk";
test("banner", () => {
chalk.level = 3;
const out = chalk.hex("#8a5cff").bold("HELLO");
expect(out).toMatchInlineSnapshot(`"[1m[38;2;138;92;255mHELLO[39m[22m"`);
});
Output: inline snapshot matches; regenerate with vitest -u after a chalk upgrade.
Version migration guide
The 4 → 5 jump is the largest break in chalk's history and the most common upgrade blocker. Plan it as a project-wide ESM migration, not a single dependency bump.
| From | To | Required change |
|---|---|---|
chalk@2 | chalk@4 | Drop the explicit new Chalk.constructor() instantiation; the default export already is the instance. |
chalk@3 | chalk@4 | Replace chalk.default with chalk (the default-as-namespace shim was removed). |
chalk@4 | chalk@5 | Convert package to ESM ("type": "module"), replace all require("chalk") with import chalk from "chalk". |
chalk@4 | chalk@5 | If template literals (chalk`…`) were used, install the extracted chalk-template package — the built-in tag was removed. |
chalk@4 | chalk@5 | Remove direct imports of supports-color; chalk re-exports it. |
ESM conversion checklist:
- Add
"type": "module"topackage.json. - Rename
.jssource files usingmodule.exportsto.cjsif they need to stay CJS — otherwise convert toimport/export. - Add
.jsextensions to relative imports (./utils→./utils.js). This is required by Node's ESM resolver. - Update Jest / ts-jest / Mocha config for ESM (or switch to Vitest, which is ESM-native).
- Replace any CJS-only deps that don't expose ESM with their dual-published replacements.
If a wholesale ESM migration is impossible, the dynamic-import escape hatch keeps you on a CJS codebase while still consuming chalk@5:
// CJS file
async function withColor() {
const { default: chalk } = await import("chalk");
console.log(chalk.red("ok"));
}
This is a per-call workaround. It works because dynamic import() is allowed in CJS, while top-level require() is not.
ESM/CJS interop & bundling
Chalk@5 is pure ESM ("type": "module" + "exports": { ".": { "import": "./source/index.js" } }) with no require condition. The bundler implications are concrete:
| Bundler / runtime | Behaviour |
|---|---|
| esbuild (Vite, tsup) | Handles ESM-only deps natively. No extra config needed. |
| webpack 5 | Works if target: "node" and experiments.outputModule = true or output is ESM. Older configs may surface "Cannot use import statement outside a module". |
| Rollup | Native ESM — no shim required. |
| tsc | Requires "module": "Node16" (or higher) plus "moduleResolution": "node16" so the compiler honours the "exports" map. |
| ts-node (legacy) | Needs --esm flag or a tsconfig.json with "module": "esnext". Many projects move to tsx instead. |
| Node 16+ | Works as long as the calling package is ESM ("type": "module" or .mjs). |
| Bun | Works natively in ESM and CJS — Bun resolves the "import" condition automatically. |
Deno (via npm:chalk) | Works; Deno only consumes ESM. |
| Cloudflare Workers | Works — Workers are ESM-only since the Modules format. Bundle size matters: chalk is small enough not to be a concern. |
The "exports" map in chalk's package.json blocks deep imports like require("chalk/source/utils") — you can only import the public surface. This was deliberate to prevent users from depending on internals.
Plugin & ecosystem coverage
Chalk's tiny core is intentionally complemented by sibling packages from the same maintainer (Sindre Sorhus). They share a consistent design language and are commonly installed together.
| Package | Role |
|---|---|
chalk-template | Tagged-template-literal syntax extracted from chalk@4 (t` {red {bold hi}} `). Install when you need terse markup in long strings. |
chalk-animation | Animated gradients (rainbow, pulse, glitch, radar, neon, karaoke). Useful for banners and easter eggs. |
supports-color | TTY capability detection. Re-exported by chalk; useful standalone for non-chalk output paths. |
ansi-escapes | Cursor moves, screen clears, hyperlinks, image rendering (iTerm2 / Kitty). Pairs with chalk for rich TUIs. |
ansi-styles | The raw style tables chalk is built on (chalk ≈ ansi-styles + a chainable wrapper). |
strip-ansi | Remove escape codes from a string before measuring width or writing to a log file. |
wrap-ansi | Word-wrap text without breaking inside escape sequences. |
cli-truncate | Truncate a styled string to a column width. |
ora | Spinner library; uses chalk for colour by default. |
boxen | Box-drawn frames around chalk-styled content. |
gradient-string | Multi-stop linear gradients across a string's characters. |
figlet | ASCII-art text rendering; pair with chalk.hex(...) for branded banners. |
These layer cleanly because they all respect chalk.level and NO_COLOR. Picking a different colour library breaks the chain — most expect chalk specifically.
Security considerations
Chalk itself is low-risk — it only emits escape sequences and has zero runtime dependencies on the 5.x line. The risks live around it.
- Untrusted strings in styled output. If user input is passed through
chalk.red(input)and the input contains its own ANSI escape codes, the result can move the cursor, clear the screen, or even (with some terminals) execute commands via OSC 8 / OSC 52 sequences. Strip withstrip-ansibefore styling:chalk.red(stripAnsi(input)). - Log injection. Coloured logs forwarded to a SIEM or aggregator may include attacker-controlled newlines that fake additional log lines. Always log JSON / NDJSON in production and reserve chalk for human-facing TTYs.
- Dependency-tree audit. chalk@5 has zero deps, but transitive imports of older
chalk@2/@3show up in many lockfiles via deeply nested packages. Runnpm ls chalkto see the full tree; deduplicate withnpm dedupeor pnpm's hoist settings. supports-colorenv variables.FORCE_COLOR=2set globally in a shared CI runner forces every chalk consumer to emit codes — useful for build logs, but corrupts any tooling that parses stdout. Set per-job, not user-wide.- OSC 8 hyperlinks in untrusted output.
terminal-linkproduces clickable URLs. Never wrap user-supplied URLs without validating the protocol —terminal-link("click", javascript:...)would render a clickable malicious link in some terminals.
Troubleshooting common errors
Error [ERR_REQUIRE_ESM]: require() of ES Module chalk — chalk@5 is ESM-only and your file is CJS. Either set "type": "module" in package.json, rename the file to .mjs, or downgrade to chalk@4.
SyntaxError: Cannot use import statement outside a module — your file uses import but package.json lacks "type": "module" and the file isn't .mjs. Add the type field or rename.
TypeError: chalk.red is not a function — you imported destructured (import { red } from "chalk"); chalk has no named exports. Use the default: import chalk from "chalk".
Output shows raw escape codes like [31mhi[39m — the terminal isn't ANSI-aware. On Windows, switch to Windows Terminal or set chalk.level = 0. On CI, the log viewer may be stripping ANSI; set FORCE_COLOR=2 and view in a real terminal.
No colour despite a colour-capable terminal — NO_COLOR=1 is set in the environment, output is being piped (| tee), or CI=true is auto-detected. Set FORCE_COLOR=1 (or 2 / 3) to override.
Colours look wrong in tmux / screen — these multiplexers strip the truecolour escape sequence by default. Set set -g default-terminal "tmux-256color" (or xterm-256color) and re-launch the session.
chalk.level is 0 in tests — Vitest and Jest run with a piped stdout, so chalk detects no TTY. Force colour via FORCE_COLOR=3 in the test command, or assert on the plain string with strip-ansi.
chalk.hex(...) outputs the nearest 16-colour approximation — chalk.level < 3. Override with chalk.level = 3 only if you're certain the terminal supports it.
Hyperlinks don't render — chalk doesn't ship a link() method. Use terminal-link from the same author family — it falls back to plain text on unsupported terminals.
Performance tuning
Chalk is fast for any realistic CLI workload but optimisations matter when wrapping millions of strings (log replay, large file processors).
- Cache style chains.
chalk.red.bold.bgWhitewalks the builder chain every call. Hoist to a constant:const errTag = chalk.red.bold.bgWhite;thenerrTag(text). chalk.level = 0is a fast-path. When colour is off, chalk skips wrapping and returns the bare string. The check isif (this.level <= 0) return string;near the top of the builder, so disabling colour costs nothing.- Avoid
chalk.hex(...)on a hot path. Hex parsing happens per call. Precompute a painter:const brand = chalk.hex("#8a5cff"); brand(text);. - Skip styling for large strings destined for a file. Writing styled output to a logfile balloons disk usage and slows reads. Branch on
process.stdout.isTTYand skip chalk when writing to a non-TTY. - Picocolors is ~2× faster on a per-call benchmark. If you wrap >10⁶ strings per second, switch — but most CLIs are I/O-bound long before chalk becomes the bottleneck.
A practical micro-benchmark:
import chalk from "chalk";
const red = chalk.red;
const N = 1_000_000;
console.time("hoisted");
for (let i = 0; i < N; i++) red("hi");
console.timeEnd("hoisted");
console.time("inline");
for (let i = 0; i < N; i++) chalk.red("hi");
console.timeEnd("inline");
Output: hoisted ~40ms, inline ~120ms on Node 22 — the chain walk is the cost.
When NOT to use this
Skip chalk when:
- The output is machine-consumed (JSON for
jq, NDJSON logs ingested by Loki, CSV piped to a spreadsheet). ANSI sequences corrupt the parse — keep stdout clean and reserve colour for stderr if needed. - The audience is non-terminal — log aggregators, CI annotation systems (GitHub Actions, GitLab), and email reports strip or mangle escape codes. Use the platform's native annotation format (
::warning::for Actions,[1;31mfor nothing). - Bundle size dominates — a CLI library imported by other libraries should prefer
picocolors(~0.4 kB) to avoid imposing chalk on every transitive consumer. - Bash already does it — for tiny scripts where
printf '\033[31m%s\033[0m\n' hidoes the job, the dependency isn't worth the install. - The terminal target is uncertain — for cross-platform tools that may run in
cmd.exe, antique consoles, or stripped Docker base images, plain text + glyphs (✓/✗/→) communicate without ANSI risk. - Browsers —
console.log("%c styled", "color: red")uses the DevTools styling API; chalk's ANSI escape codes don't render in browser dev tools.
See also
- JavaScript: chalk — chainable API, theming, CLI patterns
- Concept: pipes — how TTY detection interacts with shell pipelines