Skip to content

Terminal Styling

Theme-aware terminal styling with a chalk-compatible chainable API. Part of @silvery/ansi — use it for CLI output that respects terminal color capabilities and resolves semantic theme tokens.

Quick Start

typescript
import { style, createStyle } from "@silvery/ansi"

// Global pre-configured instance
style.bold.red("Error!")
style.primary("Deploy")

// Create your own
const s = createStyle()
s.hex("#818cf8")("Indigo")
s.bgYellow.black("Warning")

// With theme — Sterling tokens (silvery 0.20+)
const themed = createStyle({ theme })
themed["fg-accent"]("Deploy") // resolves $fg-accent from theme
themed["fg-error"]("Failed!")

createStyle()

Returns a chainable, callable style object.

typescript
import { createStyle } from "@silvery/ansi"

const s = createStyle()
s.bold.red("Error!") // bold red text
s.hex("#818cf8")("Indigo") // truecolor foreground
s.bgYellow.black("Warning") // black text on yellow background

Options

typescript
interface StyleOptions {
  /** Color level override. Auto-detected from terminal if omitted. */
  level?: "truecolor" | "256" | "ansi16" | null
  /** Theme object for $token resolution. */
  theme?: ThemeLike
}
OptionTypeDefaultDescription
level"truecolor" | "256" | "ansi16" | nullauto-detectedColor support level. null disables all color.
themeThemeLikeundefinedTheme object for token resolution

When level is omitted, createStyle() auto-detects from process.stdout, respecting NO_COLOR and FORCE_COLOR environment variables.

createPlainStyle()

Creates a style object without a theme. Equivalent to createStyle() without a theme option.

typescript
import { createPlainStyle } from "@silvery/ansi"

const s = createPlainStyle() // auto-detect color level
const s = createPlainStyle("ansi16") // force ANSI 16

Global style

A pre-configured singleton with auto-detected color level and no theme. Available immediately:

typescript
import { style } from "@silvery/ansi"

style.bold("Important")
style.red("Error")
style.primary("Deploy") // falls back to yellow (no theme)

Chainable API

Every property returns a new Style that can be chained further or called with text to apply:

typescript
const s = createStyle()

// Single modifier
s.bold("text") // bold
s.dim("text") // dim

// Chained modifiers
s.bold.italic("text") // bold + italic
s.bold.underline.red("text") // bold + underline + red

Modifiers

PropertySGR OpenSGR CloseDescription
bold122Bold intensity
dim222Faint/dim
italic323Italic
underline424Underline
inverse727Swap fg/bg
hidden828Hidden text
strikethrough929Strikethrough

Foreground Colors

Standard ANSI foreground colors:

PropertyCodeColor
black30Black
red31Red
green32Green
yellow33Yellow
blue34Blue
magenta35Magenta
cyan36Cyan
white37White
blackBright90Bright black
gray / grey90Gray (alias)
redBright91Bright red
greenBright92Bright green
yellowBright93Bright yellow
blueBright94Bright blue
magentaBright95Bright magenta
cyanBright96Bright cyan
whiteBright97Bright white

Background Colors

Same names prefixed with bg:

typescript
s.bgRed("text")
s.bgBlueBright("text")
s.bgBlack.whiteBright("text")

Available: bgBlack, bgRed, bgGreen, bgYellow, bgBlue, bgMagenta, bgCyan, bgWhite, bgBlackBright, bgRedBright, bgGreenBright, bgYellowBright, bgBlueBright, bgMagentaBright, bgCyanBright, bgWhiteBright.

Color Methods

For arbitrary colors beyond the 16 ANSI palette:

typescript
// Hex colors (foreground and background)
s.hex("#ff6347")("Tomato")
s.bgHex("#1e1e2e")("Dark bg")

// RGB values (foreground and background)
s.rgb(255, 99, 71)("Tomato")
s.bgRgb(30, 30, 46)("Dark bg")

// ANSI 256 color index
s.ansi256(196)("Bright red")
s.bgAnsi256(17)("Navy bg")
MethodSignatureDescription
hex(color: string) => StyleForeground from hex (#rrggbb)
bgHex(color: string) => StyleBackground from hex
rgb(r, g, b) => StyleForeground from RGB values
bgRgb(r, g, b) => StyleBackground from RGB values
ansi256(code: number) => StyleForeground from 256-color index
bgAnsi256(code: number) => StyleBackground from 256-color index

Theme Token Resolution

When a theme is provided to createStyle(), Sterling $tokens resolve to their theme colors. Both kebab and camelCase forms work.

typescript
import { createStyle } from "@silvery/ansi"
import { defaultDarkTheme } from "silvery/theme"

const s = createStyle({ theme: defaultDarkTheme })

s["fg-accent"]("Deploy") // resolves theme["fg-accent"] -> hex -> ANSI
s["fg-error"]("Failed!") // resolves theme["fg-error"]  -> hex -> ANSI
s["fg-success"]("Passed")
s["fg-muted"]("(3 files)")
s["fg-warning"]("Caution")
s["fg-info"]("Note")
s["fg-link"]("https://...") // resolves theme["fg-link"] + adds underline
s.bold["fg-accent"]("DEPLOY") // chain modifiers with tokens

Available Tokens

The $token resolver works on Sterling's flat hyphen-keys. The most common ones for CLI output:

TokenWithout theme (fallback)With theme
fg-accentyellow (ANSI 33)theme["fg-accent"] as hex
fg-errorred (ANSI 31)theme["fg-error"] as hex
fg-warningyellow (ANSI 33)theme["fg-warning"] as hex
fg-successgreen (ANSI 32)theme["fg-success"] as hex
fg-infocyan (ANSI 36)theme["fg-info"] as hex
fg-muteddim (SGR 2)theme["fg-muted"] as hex
fg-linkblue + underline (ANSI 34)theme["fg-link"] as hex + underline
border-defaultgray (ANSI 90)theme["border-default"] as hex
fgterminal default fgtheme.fg as hex
bgterminal default bgtheme.bg as hex

For the complete flat-token list (selection, inverse, surface stack, status fills, accent state variants, etc.) see the Sterling primer. When no theme is provided, tokens fall back to standard ANSI codes.

resolve()

Programmatically resolve a token to its hex value:

typescript
const s = createStyle({ theme })

s.resolve("fg-accent") // theme["fg-accent"]
s.resolve("$fg-accent") // same — $ prefix also accepted
s.resolve("$color0") // theme.palette[0]
s.resolve("$bg-surface-raised") // theme["bg-surface-raised"]

Color Level Detection and Degradation

createStyle() auto-detects the terminal's color capability and degrades gracefully:

LevelCapabilityHex/RGB handling
"truecolor"16 million colors38;2;R;G;B — exact color
"256"256 colors38;5;N — nearest in 6x6x6 cube
"ansi16"16 colors30--37 / 90--97 — nearest ANSI
nullNo colorAll styling stripped, plain text returned

The degradation from truecolor to 256 uses the 6x6x6 color cube (indices 16--231) and the 24-shade gray ramp (indices 232--255). The degradation from 256 to basic uses Euclidean distance in RGB space against the standard ANSI 16 color values.

Forcing a Color Level

typescript
// Force truecolor regardless of terminal
const s = createStyle({ level: "truecolor" })

// Force no color (useful for tests or file output)
const s = createStyle({ level: null })

// Force ANSI 16 for maximum compatibility
const s = createStyle({ level: "ansi16" })

Chalk-compatible level Property

The level property on Style instances is mutable and uses chalk's numeric convention:

typescript
const s = createStyle()
s.level // 0=none, 1=basic, 2=256, 3=truecolor

s.level = 0 // disable color
s.level = 3 // force truecolor

Setting level affects all chains derived from the same createStyle() call.

ThemeLike Interface

The theme option accepts any object with string-valued properties. It does not require the full Theme type:

typescript
interface ThemeLike {
  palette?: string[]
}

This means you can pass a partial theme or any plain object:

typescript
const s = createStyle({
  theme: {
    "fg-accent": "#818cf8",
    "fg-error": "#f7768e",
    "fg-success": "#9ece6a",
  } as any, // or a real Sterling Theme
})

s["fg-accent"]("Styled") // uses #818cf8
s["fg-warning"]("Warn") // falls back to yellow (ANSI 33) — not in theme

Template Literal Support

Style functions accept template literals:

typescript
const name = "world"
s.bold`Hello, ${name}!` // bold "Hello, world!"
s.red`Error: ${code}` // red text with interpolation

Examples

CLI Progress Output

typescript
import { style } from "@silvery/ansi"

console.log(style.bold("Building..."))
console.log(style.green("  ✓ Compiled 42 files"))
console.log(style.yellow("  ⚠ 3 warnings"))
console.log(style.red("  ✗ 1 error"))
console.log(style.dim("  Duration: 1.2s"))

Theme-aware Status Bar

typescript
import { createStyle } from "@silvery/ansi"
import { defaultDarkTheme } from "silvery/theme"

const s = createStyle({ theme: defaultDarkTheme })

function statusLine(branch: string, files: number, errors: number) {
  const parts = [
    s["fg-accent"](` ${branch} `),
    s["fg-muted"](` ${files} files`),
    errors > 0 ? s["fg-error"](` ${errors} errors`) : s["fg-success"](" clean"),
  ]
  return parts.join(s["fg-muted"](" | "))
}