Style
Terminal styling and layout utilities for CLI output.
@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 start
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. On Bun they call Bun.stringWidth; on Node and Deno a JavaScript fallback matches Bun for common terminal text (ASCII, ANSI escapes, CJK, emoji, combining marks), while exotic clusters may measure differently. table() uses headers for its column count, supports per-column alignment, and accepts readonly (as const) headers, rows, and alignments.
Reference
type ColorMode = "auto" | "always" | "never";
type ColorDepth = "truecolor" | "256" | "16" | "none";The root exports style, createStyle, named chainables, fg, bg, link, layout helpers, and their option and input types. See the source inventory in packages/style/src/index.ts.
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.