All docs
Concepts · Capability detection

Capability detection

Every Caret component asks one question before painting: what does this terminal actually support? The answer comes from lib/capability.ts, a single synchronous read that every primitive consults. You don't enable any of this — you'd have to go out of your way to break it.

What it detects

SignalSourceEffect
isTTYprocess.stdout.isTTYPipe-aware fallback — when piped, components emit plain text without escape codes
hasColorNO_COLOR env var, FORCE_COLOR, CI heuristicsStrips chalk colors entirely when set
truecolorCOLORTERM=truecolor / TERM=*-256colorPicks 24-bit RGB vs 256-color vs ANSI 16
unicodeLANG / LC_ALL contains UTF-8Falls glyphs back to ASCII (✓ → +, │ → |) when missing
columnsprocess.stdout.columnsWraps tables, banners, paragraphs to fit narrow terminals
reducedMotionCARET_REDUCED_MOTION, prefers-reduced-motion shimDisables spinner / typewriter / reveal animations
dumbTERM=dumbPlain output, no escape codes, no animation

Reading capability in your own code

If you're authoring a custom component or extending a Caret one, call capability() directly. It's cheap — synchronous, no cache invalidation needed.

ts
import { capability } from './caret/lib/capability'

const cap = capability()

if (!cap.isTTY) {
  // Output is being piped — emit plain JSON, not pretty terminal UI.
  console.log(JSON.stringify(result))
  return
}

if (cap.dumb || !cap.unicode) {
  // Use ASCII fallbacks throughout this command.
}

The color fallback chain

Components walk the same chain in this exact order — first match wins:

  1. NO_COLOR set? Plain text, no escape codes at all.
  2. Not a TTY? Plain text — pipe-friendly.
  3. Truecolor terminal? Brand accent emitted as 24-bit RGB; semantic colors as ANSI named.
  4. 256-color terminal? Brand accent quantized to the closest 256-color slot.
  5. ANSI 16 terminal? Brand accent maps to the closest named ANSI color.
  6. Dumb terminal? Plain text, hierarchy expressed via symbols only.

Environment variables

VariableEffect
NO_COLOR=1Disable color in all Caret output (also strips third-party chalk via Caret's paint helper)
FORCE_COLOR=3Force truecolor even when stdout is not a TTY (CI snapshots)
CARET_REDUCED_MOTION=1Disable all motion regardless of OS preference
CARET_NO_NOTIFY=1Disable system notifications from spinner / prompts.notifyOnWait
TERM=dumbPlain output, ASCII glyphs, no animation
Always TTY-aware
Caret components NEVER write escape codes when process.stdout.isTTY is false. Pipe a Caret CLI through grep, jq, or save it to a file — scrollback stays clean. The manifesto rule: "stdout is for data, stderr is for messages."

Testing under different capabilities

For local development, the easiest way to test capability paths:

sh
# No color
NO_COLOR=1 my-cli deploy

# Force truecolor in CI
FORCE_COLOR=3 my-cli deploy

# Pipe — non-interactive
my-cli deploy | cat

# Dumb terminal
TERM=dumb my-cli deploy

Each variant should produce sensible output. If you find a component that emits escape codes through a pipe, it's a bug — file an issue.

Continue with Motion — the animation tokens that capability detection gates.