Style
Terminal styling, colors, hyperlinks, and ANSI-aware text layout
@crustjs/style exports ANSI-aware style functions, dynamic colors, hyperlinks, and text layout helpers. Default exports read terminal capability inputs on each call; createStyle() creates an instance with fixed settings.
Install
npm install @crustjs/styleQuick example
import { bold, fg, green, style } from "@crustjs/style";
console.log(green("OK"), bold("Build complete")); // OK Build complete
console.log(style.bold.red("Build failed")); // Build failed
console.log(fg("brand", "#4fa83d")); // brandNamed styles are callable and chainable. Chains also support tagged templates.
Styling
bold("direct");
bold`tagged ${value}`;
bold.red.bgYellow("chained");Modifiers are bold, dim, italic, underline, inverse, hidden, and strikethrough. The package also exports the standard 16 foreground and 16 background names.
A chain is a ChainableStyleFn, which can be passed where a (text: string) => string StyleFn is expected. Null and undefined inputs to chainables produce an empty string.
Dynamic colors
fg("error", "#ff0000");
fg("ocean", "rgb(0, 128, 255)");
fg("coral", [255, 127, 80]);
style.bold.fg("rebeccapurple")("note");fg() and bg() accept three- or six-digit hex, RGB strings, RGB tuples, and named CSS colors. Invalid input throws TypeError, including when the text is empty.
The resolved ColorDepth is "truecolor" | "256" | "16" | "none". Read style.colorDepth, colorsEnabled, or trueColorEnabled to inspect it.
Color control
import { createStyle } from "@crustjs/style";
const color = createStyle({ mode: "always" });
const plain = createStyle({ mode: "never" });
console.log(JSON.stringify(color.red("error"))); // "\u001b[31merror\u001b[39m"
console.log(JSON.stringify(plain.red("error"))); // "error"Default exports use process.stdout.isTTY, NO_COLOR, FORCE_COLOR, COLORTERM, and TERM. FORCE_COLOR=0 and FORCE_COLOR=false disable all ANSI, while any other FORCE_COLOR value forces ANSI and overrides NO_COLOR. Without FORCE_COLOR, a non-empty NO_COLOR disables colors; forced values 1, 2, and 3 select 16-color, 256-color, and truecolor depth.
createStyle({ mode: "always" }) enables all ANSI at truecolor depth. mode: "never" returns plain text, and mode: "auto" accepts overrides for deterministic tests.
Hyperlinks (OSC 8)
link("docs", "https://crustjs.com");
link(style.underline("Crust"), "https://crustjs.com", { id: "docs" });Links emit OSC 8 sequences when capability checks allow them and otherwise return the label. URLs are validated even when emission is off, and NO_COLOR does not disable links.
Layout
import { link, padEnd, style as layoutStyle, table } from "@crustjs/style";
console.log(padEnd(layoutStyle.bold("Name"), 10)); // "Name "
console.log(table(["Name", "State"], [["build", "ok"]]));
// | Name | State |
// |-------|-------|
// | build | ok |
console.log(link("docs", "https://crustjs.com")); // docspadStart, padEnd, center, and stringWidth measure visible terminal columns rather than ANSI bytes. Bun uses Bun.stringWidth; the Node and Deno fallback may differ for uncommon grapheme clusters. table() uses headers for its column count, supports per-column alignment, and accepts readonly (as const) headers, rows, and alignments.
Reference
The package entry point lists every export, including the ColorMode and ColorDepth types.
Prop
Type
Prop
Type
Fine print
const chain = style.bold.red;
const open = chain.open;
const close = chain.close;open and close are raw static ANSI strings and do not recheck capabilities. Use chain(text) for capability-aware, nesting-safe output.
Direct fg(text, input) requires string text. Chainables accept other stringifiable values, return "" for nullish or empty input, and still validate dynamic color input.