JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 847
  • Score
    100M100P100Q106458F
  • License MIT

Terminal styling foundation for the Crust CLI framework

Package Exports

  • @crustjs/style

Readme

@crustjs/style

Terminal styling foundation for the Crust CLI framework.

@crustjs/style provides ANSI-safe styling primitives, terminal capability awareness, layout helpers, and a semantic markdown theme — so CLI output remains readable, aligned, and consistent across color and no-color environments.

Zero runtime dependencies.

Install

bun add @crustjs/style

Quick Start

import { style } from "@crustjs/style";

// The default `style` instance auto-detects color support
console.log(style.bold("Build succeeded"));
console.log(style.red("Error: missing argument"));
console.log(style.dim("hint: use --help for usage"));
console.log(style.bold.red("Critical failure"));

Primitive Styling

Direct styling functions that always emit ANSI codes (useful when you control output directly):

import { bold, red, italic, bgYellow } from "@crustjs/style";

console.log(bold("important"));
console.log(red("error"));
console.log(italic("note"));
console.log(bgYellow("highlighted"));

Available Modifiers

bold, dim, italic, underline, inverse, hidden, strikethrough

Available Colors

Foreground: black, red, green, yellow, blue, magenta, cyan, white, gray, brightRed, brightGreen, brightYellow, brightBlue, brightMagenta, brightCyan, brightWhite

Background: bgBlack, bgRed, bgGreen, bgYellow, bgBlue, bgMagenta, bgCyan, bgWhite, bgBrightBlack, bgBrightRed, bgBrightGreen, bgBrightYellow, bgBrightBlue, bgBrightMagenta, bgBrightCyan, bgBrightWhite

Composing Styles

import { applyStyle, composeStyles, boldCode, redCode } from "@crustjs/style";

const boldRed = composeStyles(boldCode, redCode);
console.log(applyStyle("critical error", boldRed));

Nested styles are handled safely — no style bleed across boundaries.

Dynamic Colors (Truecolor)

Use any RGB or hex color via 24-bit truecolor ANSI sequences:

import { rgb, bgRgb, hex, bgHex } from "@crustjs/style";

// RGB values (0–255)
console.log(rgb("ocean", 0, 128, 255));
console.log(bgRgb("warning", 255, 128, 0));

// Hex colors (#RGB or #RRGGBB)
console.log(hex("error", "#ff0000"));
console.log(hex("short", "#f00"));
console.log(bgHex("highlight", "#ff8800"));

Pair Factories

Create reusable AnsiPair objects for composition:

import { rgbCode, bgRgbCode, hexCode, bgHexCode, applyStyle, composeStyles, boldCode } from "@crustjs/style";

const coral = rgbCode(255, 127, 80);
console.log(applyStyle("coral text", coral));

const boldCoral = composeStyles(boldCode, hexCode("#ff7f50"));
console.log(applyStyle("bold coral", boldCoral));

Style Instance

Dynamic colors on createStyle instances respect mode and truecolor detection:

import { createStyle } from "@crustjs/style";

const s = createStyle({ mode: "always" });
console.log(s.rgb("text", 255, 0, 0));
console.log(s.hex("text", "#ff0000"));
console.log(s.bgRgb("text", 0, 128, 255));
console.log(s.bgHex("text", "#0080ff"));

In "auto" mode, dynamic colors are emitted only when the terminal supports truecolor (detected via COLORTERM=truecolor|24bit or TERM containing truecolor, 24bit, or -direct). When truecolor is not detected, dynamic color methods return plain text while standard 16-color methods continue to work.

In "never" mode, all dynamic color methods return plain text. In "always" mode, truecolor sequences are always emitted.

Terminal Compatibility

Dynamic colors use truecolor (24-bit) ANSI sequences. There is no automatic fallback to 256 or 16 colors. On terminals that do not support truecolor:

  • Colors may be approximated to the nearest supported color
  • Colors may be silently ignored (text renders in default color)
  • No runtime errors will occur

Color Modes

Control when ANSI codes are emitted using createStyle:

import { createStyle } from "@crustjs/style";

// Auto-detect (default) — respects NO_COLOR and TTY status
const auto = createStyle({ mode: "auto" });

// Always emit ANSI codes
const color = createStyle({ mode: "always" });
console.log(color.red("always red"));
console.log(color.bold.red("always bold red"));

// Never emit ANSI codes — returns plain text
const plain = createStyle({ mode: "never" });
console.log(plain.red("just text")); // "just text"

The default style export uses "auto" mode:

  • Emits ANSI when stdout is a TTY and NO_COLOR is not set
  • Returns plain text otherwise

Deterministic Testing

Inject capability overrides for predictable test output:

const testStyle = createStyle({
  mode: "auto",
  overrides: { isTTY: true, noColor: undefined },
});

// Include truecolor overrides for dynamic color testing
const truecolorStyle = createStyle({
  mode: "auto",
  overrides: { isTTY: true, noColor: undefined, colorTerm: "truecolor" },
});

Text Utilities

ANSI-aware text measurement and layout:

import {
  stripAnsi,
  visibleWidth,
  wrapText,
  padStart,
  padEnd,
  center,
} from "@crustjs/style";

// Strip ANSI escape sequences
stripAnsi("\x1b[1mhello\x1b[22m"); // "hello"

// Measure visible width (ignores ANSI codes, counts CJK as 2)
visibleWidth("\x1b[31mhello\x1b[39m"); // 5

// Wrap text to a visible width, preserving active styles across breaks
wrapText("a long line of styled text", 20);
wrapText("force break mode", 10, { wordBreak: false });

// ANSI-safe padding and alignment
padStart("42", 6);   // "    42"
padEnd("name", 10);  // "name      "
center("title", 20); // "       title        "

Block Helpers

Format structured blocks for terminal output:

Lists

import { unorderedList, orderedList, taskList } from "@crustjs/style";

unorderedList(["apples", "bananas", "cherries"]);
// • apples
// • bananas
// • cherries

orderedList(["first", "second", "third"]);
// 1. first
// 2. second
// 3. third

taskList([
  { text: "Write tests", checked: true },
  { text: "Update docs", checked: false },
]);
// [x] Write tests
// [ ] Update docs

Lists support indent for nesting and custom markers.

Tables

import { table } from "@crustjs/style";

table(
  ["Name", "Version", "Status"],
  [
    ["core", "1.2.0", "stable"],
    ["style", "0.1.0", "new"],
  ],
);
// | Name  | Version | Status |
// | ----- | ------- | ------ |
// | core  | 1.2.0   | stable |
// | style | 0.1.0   | new    |

Tables support per-column alignment ("left", "right", "center"), custom padding, and ANSI-styled cell content with correct alignment.

Markdown Theme

Semantic theme slots for styling GFM (GitHub Flavored Markdown) constructs:

import { defaultTheme } from "@crustjs/style";

// Apply theme slots to extracted markdown content
console.log(defaultTheme.heading1("Getting Started"));
console.log(defaultTheme.strong("important"));
console.log(defaultTheme.inlineCode("npm install"));
console.log(defaultTheme.linkText("docs") + " " + defaultTheme.linkUrl("https://crustjs.com"));

Custom Themes

Override specific slots while inheriting defaults:

import { createTheme } from "@crustjs/style";

const theme = createTheme({
  style: { mode: "always" },
  overrides: {
    heading1: (text) => `>>> ${text.toUpperCase()} <<<`,
    strong: (text) => `**${text}**`,
  },
});

Theme Slots

The MarkdownTheme interface covers 30 GFM slots:

Category Slots
Headings heading1 through heading6
Text text, emphasis, strong, strongEmphasis, strikethrough
Code inlineCode, codeFence, codeInfo, codeText
Links linkText, linkUrl, autolink
Lists listMarker, orderedListMarker, taskChecked, taskUnchecked
Blockquotes blockquoteMarker, blockquoteText
Tables tableHeader, tableCell, tableBorder
Other thematicBreak, imageAltText, imageUrl

Theme slots are parser-agnostic string => string functions. Markdown parsing and AST handling belong to a separate consumer package — @crustjs/style provides the presentation layer only.

Documentation

See the full docs at crustjs.com.

License

MIT