Skip to content

Colors

Chain colors and modifiers chalk-style. Each chain step builds a fresh callable formatter — lazy, stateless, reusable.

import { colors, createColors, type ColorName, type ChainFormatter } from "@mongez/copper";
colors.red("error");
colors.red.bold("error");
colors.bgWhite.black.bold(" WARN ");
colors.gray.italic("hint");
colors.red.bold.underline("critical");
const danger = colors.red.bold.underline;
danger("File not found");
danger("Connection refused");

Composition (colors.red(colors.bold(x))) produces identical ANSI output and is the natural shape for indexed access (colors[name](x) where name is a runtime ColorName). Every chain example below has a composition equivalent — pick whichever reads better at the call site.

Modifiers

FunctionEffect
bold(s)Bold
dim(s)Faint
italic(s)Italic
underline(s)Underline
strikethrough(s)Strikethrough
inverse(s)Swap fg/bg
hidden(s)Invisible (still copies on select)
reset(s)Wraps with CSI 0 on both sides

Palette (foreground)

Each row also has *Bright, bg*, and bgBright* variants unless noted.

FamilyNames
Neutralblack, gray, white, blackBright, whiteBright
Slateslate, slateBright
Redred, redBright
Orangeorange, orangeBright
Yellowyellow, yellowBright
Goldgold, goldBright
Chocolatechocolate, chocolateBright
Brownbrown, brownBright
Greengreen, greenBright
Limelime, limeBright
Tealteal, tealBright
Cyancyan, cyanBright
Blueblue, blueBright
Magentamagenta, magentaBright
Purplepurple, purpleBright
Pinkpink, pinkBright
Lavenderlavender, lavenderBright
Indigoindigo, indigoBright

The standard 4-bit colors (red/green/…/white) use the 30-37 / 90-97 sequences. The extended hues (lime, teal, brown, gold, …) use 256-color sequences (\x1b[38;5;Nm) so terminals must support 256 colors — which all common modern terminals do.

createColors(enabled?) — force on or off

const off = createColors(false);
off.red("hi"); // "hi" (no ANSI added)
off.isColorSupported; // false
const on = createColors(true);
on.red("hi"); // "\x1b[31mhi\x1b[39m"

Use this to:

  • Strip ANSI from a colored builder before snapshotting a test (createColors(false).red(x)).
  • Force ANSI in a sub-process whose stdout isn’t a TTY but is being captured.
  • Build a separate “diff” or “patch” themed instance with its own palette.

Typed color names (the composition case)

When the color name is decided at runtime, indexed access + composition is the right shape — chains are a compile-time-only thing:

import { colors, type ColorName } from "@mongez/copper";
function paint(level: "ok" | "fail", text: string) {
const c: ColorName = level === "ok" ? "green" : "red";
return colors[c](text);
}
// Add a modifier via composition:
colors[c](colors.bold(text));

ColorName is derived from the COLOR_NAMES tuple — it excludes the meta keys isColorSupported and createColors so colors[name] is always a callable formatter.

Replacing chalk

Near-1:1 import swap thanks to chaining:

- import chalk from "chalk";
+ import { colors } from "@mongez/copper";
- chalk.red(text);
+ colors.red(text);
- chalk.red.bold(text);
+ colors.red.bold(text);
- chalk.bgWhite.black.bold(text);
+ colors.bgWhite.black.bold(text);

For chalk’s tagged-template syntax (chalk\{red ${value}}`), use colorize-template` against the copper instance:

import { createColorize } from "colorize-template";
import { colors } from "@mongez/copper";
const colorize = createColorize(colors);
colorize`{red.bold Build} took {yellow ${ms}ms}`;

Composition safety

When you nest colors.x(colors.y(text)) (or chain colors.x.y(text)) and text already contains the inner closer code, copper rewrites internal closers to re-open the outer color — so chains do not leak:

colors.bold(colors.red("inner"));
// "\x1b[1m\x1b[31minner\x1b[39m\x1b[22m" ← red closer (39m) does not eat bold