Style
Terminal styling, layout utilities, and markdown theme for CLI output.
The style package provides ANSI-safe styling primitives, terminal capability awareness, text layout helpers, and a semantic markdown theme. It has zero runtime dependencies.
Install
bun add @crustjs/styleExports
Style Instance
| Export | Description |
|---|---|
style | Default auto-mode style instance |
createStyle(options?) | Create a configured style instance |
Modifiers
| Export | Description |
|---|---|
bold(text) | Bold text |
dim(text) | Dimmed text |
italic(text) | Italic text |
underline(text) | Underlined text |
inverse(text) | Inverted foreground/background |
hidden(text) | Hidden text |
strikethrough(text) | Strikethrough text |
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
Dynamic Colors (Truecolor)
Direct styling functions using 24-bit truecolor ANSI sequences:
| Export | Description |
|---|---|
rgb(text, r, g, b) | Apply truecolor foreground RGB color |
bgRgb(text, r, g, b) | Apply truecolor background RGB color |
hex(text, hexColor) | Apply truecolor foreground hex color (#RGB or #RRGGBB) |
bgHex(text, hexColor) | Apply truecolor background hex color |
ANSI pair factories for composition:
| Export | Description |
|---|---|
rgbCode(r, g, b) | Create foreground AnsiPair from RGB values |
bgRgbCode(r, g, b) | Create background AnsiPair from RGB values |
hexCode(hex) | Create foreground AnsiPair from hex string |
bgHexCode(hex) | Create background AnsiPair from hex string |
parseHex(hex) | Parse a hex color string into [r, g, b] tuple |
Style Engine
| Export | Description |
|---|---|
applyStyle(text, pair) | Apply an ANSI pair with nesting-safe composition |
composeStyles(...pairs) | Compose multiple ANSI pairs into one |
Capability Detection
| Export | Description |
|---|---|
resolveCapability(mode, overrides?) | Resolve whether colors should be emitted |
resolveTrueColor(mode, overrides?) | Resolve whether truecolor (24-bit) sequences should be emitted |
Text Utilities
| Export | Description |
|---|---|
visibleWidth(text) | Compute visible width (ANSI-aware, CJK-aware) |
wrapText(text, width, options?) | Wrap text by visible width with style continuity |
padStart(text, width, char?) | Left-pad to visible width |
padEnd(text, width, char?) | Right-pad to visible width |
center(text, width, char?) | Center-align to visible width |
To strip ANSI escape sequences from a string, use Bun's built-in Bun.stripANSI(text) function.
Block Helpers
| Export | Description |
|---|---|
unorderedList(items, options?) | Format an unordered list with bullet markers |
orderedList(items, options?) | Format an ordered list with number markers |
taskList(items, options?) | Format a task list with check markers |
table(headers, rows, options?) | Format a column-aligned table |
Markdown Theme
| Export | Description |
|---|---|
defaultTheme | Default auto-mode markdown theme |
createMarkdownTheme(options?) | Create a theme with optional overrides |
buildDefaultMarkdownTheme(style) | Build a default theme from a style instance |
Types
| Type | Description |
|---|---|
AnsiPair | Open/close ANSI escape sequence pair |
ColorMode | "auto" | "always" | "never" |
StyleOptions | Options for createStyle |
CapabilityOverrides | Injectable TTY/NO_COLOR overrides for testing |
TrueColorOverrides | Injectable COLORTERM/TERM overrides for truecolor testing |
StyleFn | (text: string) => string style function |
StyleInstance | Configured style object with all styling methods |
WrapOptions | Options for wrapText |
ColumnAlignment | "left" | "right" | "center" |
TableOptions | Options for table |
UnorderedListOptions | Options for unorderedList |
OrderedListOptions | Options for orderedList |
TaskListItem | { text: string; checked: boolean } |
TaskListOptions | Options for taskList |
MarkdownTheme | Semantic theme contract with 30 GFM slots |
PartialMarkdownTheme | Partial override type for theme customization |
ThemeSlotFn | (value: string) => string theme slot function |
CreateMarkdownThemeOptions | Options for createMarkdownTheme |
Usage
Basic Styling
import { bold, red, italic, style } from "@crustjs/style";
console.log(bold("Build succeeded"));
console.log(red("Error: missing argument"));
console.log(italic("note: check your config"));
console.log(style.bold.red("Critical failure"));Mode-Aware Styling
import { createStyle } from "@crustjs/style";
// Auto-detect terminal capabilities (default)
const s = createStyle();
console.log(s.bold("hello"));
// Force colors on
const color = createStyle({ mode: "always" });
console.log(color.red("always red"));
console.log(color.bold.red("always bold red"));
// Plain text output
const plain = createStyle({ mode: "never" });
console.log(plain.red("error")); // "error" (no ANSI)Composing Styles
import { applyStyle, composeStyles, boldCode, redCode } from "@crustjs/style";
const boldRed = composeStyles(boldCode, redCode);
console.log(applyStyle("critical", boldRed));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 per channel)
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(bgHex("highlight", "#ff8800"));Pair factories work with applyStyle and composeStyles:
import { rgbCode, hexCode, 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));On createStyle instances, dynamic colors 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"));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 normally.
Invalid inputs throw immediately: RangeError for out-of-range RGB values, TypeError for malformed hex strings.
Dynamic colors use truecolor (24-bit) ANSI sequences with no automatic fallback to 256 or 16 colors. On unsupported terminals, colors may be approximated or silently ignored — no runtime errors will occur.
Text Layout
import { visibleWidth, wrapText, padEnd, table } from "@crustjs/style";
// Measure and wrap styled text
const width = visibleWidth(styledText);
const wrapped = wrapText(longText, 60);
// Formatted table output
const output = table(
["Command", "Description"],
[
["build", "Build all packages"],
["test", "Run test suite"],
],
);
console.log(output);Markdown Theme
import { defaultTheme, createMarkdownTheme } from "@crustjs/style";
// Use the default theme
console.log(defaultTheme.heading1("Getting Started"));
console.log(defaultTheme.strong("important"));
console.log(defaultTheme.inlineCode("bun install"));
// Customize specific slots
const custom = createMarkdownTheme({
style: { mode: "always" },
overrides: {
heading1: (text) => `# ${text.toUpperCase()}`,
},
});Design
@crustjs/style is the presentation layer for Crust terminal output. It is parser-agnostic — markdown parsing, AST transformation, and syntax highlighting belong to separate consumer packages. This package provides the styling and layout primitives that those packages build on.
The architecture follows four layers:
- Styling Core — ANSI code mapping, nesting-safe application, and composition
- Capability Layer — Runtime color mode resolution (
auto/always/never) with truecolor detection - Layout Layer — ANSI-aware width, wrapping, padding, and block formatting
- Theme Layer — Semantic style slots for GFM constructs
The NO_COLOR environment variable and non-TTY detection are respected in auto mode. All APIs are pure and side-effect free.