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
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.
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 backgroundOptions
interface StyleOptions {
/** Color level override. Auto-detected from terminal if omitted. */
level?: "truecolor" | "256" | "ansi16" | null
/** Theme object for $token resolution. */
theme?: ThemeLike
}| Option | Type | Default | Description |
|---|---|---|---|
level | "truecolor" | "256" | "ansi16" | null | auto-detected | Color support level. null disables all color. |
theme | ThemeLike | undefined | Theme 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.
import { createPlainStyle } from "@silvery/ansi"
const s = createPlainStyle() // auto-detect color level
const s = createPlainStyle("ansi16") // force ANSI 16Global style
A pre-configured singleton with auto-detected color level and no theme. Available immediately:
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:
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 + redModifiers
| Property | SGR Open | SGR Close | Description |
|---|---|---|---|
bold | 1 | 22 | Bold intensity |
dim | 2 | 22 | Faint/dim |
italic | 3 | 23 | Italic |
underline | 4 | 24 | Underline |
inverse | 7 | 27 | Swap fg/bg |
hidden | 8 | 28 | Hidden text |
strikethrough | 9 | 29 | Strikethrough |
Foreground Colors
Standard ANSI foreground colors:
| Property | Code | Color |
|---|---|---|
black | 30 | Black |
red | 31 | Red |
green | 32 | Green |
yellow | 33 | Yellow |
blue | 34 | Blue |
magenta | 35 | Magenta |
cyan | 36 | Cyan |
white | 37 | White |
blackBright | 90 | Bright black |
gray / grey | 90 | Gray (alias) |
redBright | 91 | Bright red |
greenBright | 92 | Bright green |
yellowBright | 93 | Bright yellow |
blueBright | 94 | Bright blue |
magentaBright | 95 | Bright magenta |
cyanBright | 96 | Bright cyan |
whiteBright | 97 | Bright white |
Background Colors
Same names prefixed with bg:
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:
// 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")| Method | Signature | Description |
|---|---|---|
hex | (color: string) => Style | Foreground from hex (#rrggbb) |
bgHex | (color: string) => Style | Background from hex |
rgb | (r, g, b) => Style | Foreground from RGB values |
bgRgb | (r, g, b) => Style | Background from RGB values |
ansi256 | (code: number) => Style | Foreground from 256-color index |
bgAnsi256 | (code: number) => Style | Background 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.
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 tokensAvailable Tokens
The $token resolver works on Sterling's flat hyphen-keys. The most common ones for CLI output:
| Token | Without theme (fallback) | With theme |
|---|---|---|
fg-accent | yellow (ANSI 33) | theme["fg-accent"] as hex |
fg-error | red (ANSI 31) | theme["fg-error"] as hex |
fg-warning | yellow (ANSI 33) | theme["fg-warning"] as hex |
fg-success | green (ANSI 32) | theme["fg-success"] as hex |
fg-info | cyan (ANSI 36) | theme["fg-info"] as hex |
fg-muted | dim (SGR 2) | theme["fg-muted"] as hex |
fg-link | blue + underline (ANSI 34) | theme["fg-link"] as hex + underline |
border-default | gray (ANSI 90) | theme["border-default"] as hex |
fg | terminal default fg | theme.fg as hex |
bg | terminal default bg | theme.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:
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:
| Level | Capability | Hex/RGB handling |
|---|---|---|
"truecolor" | 16 million colors | 38;2;R;G;B — exact color |
"256" | 256 colors | 38;5;N — nearest in 6x6x6 cube |
"ansi16" | 16 colors | 30--37 / 90--97 — nearest ANSI |
null | No color | All 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
// 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:
const s = createStyle()
s.level // 0=none, 1=basic, 2=256, 3=truecolor
s.level = 0 // disable color
s.level = 3 // force truecolorSetting 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:
interface ThemeLike {
palette?: string[]
}This means you can pass a partial theme or any plain object:
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 themeTemplate Literal Support
Style functions accept template literals:
const name = "world"
s.bold`Hello, ${name}!` // bold "Hello, world!"
s.red`Error: ${code}` // red text with interpolationExamples
CLI Progress Output
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
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"](" | "))
}