Crust logoCrust

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/style

Quick 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")); // brand

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

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")); // docs

padStart, 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.

On this page