Ansi.md

ENV & Capabilities

Here at Ansi.md we love COLOR and want to use it as much as possible. As such, there are three questions that many cli apps need to answer:

  1. Does the user even WANT color? Often the answer is no, like when piping to a file.
  2. HOW MANY colors (16/256/16M) can the terminal handle?
  3. If we choose to display color, is the terminal DARK or LIGHT? The last thing we want to do is use white text if the terminal has a light background.

This is a colossal pain, so so so complicated, and flat out byzantine. We stand on the shoulders of giants and they tickle us relentlessly. I hesitate here because I find it difficult to even begin.

1. Does the user WANT color?

Your app is running. All your hard work is paying off, you have real users. Important question, does the user WANT color or should we stick with black and white? Handy overview:

  • Is stdout a tty? - If stdout is a tty, the user is NOT redirecting to a file or piping to another command. Sorry for the double negatives, but if stdout isn't a tty you may want to turn color off. Every language and library has a helper for this, you just gotta look for it. Note that some apps that are really focused on display might choose to leave color on anyway, like my tennis app. The user asked for a beautiful table, sometimes they want to pipe it into less. This is super common.

  • $FORCE_COLOR=1 or $NO_COLOR=1 - I like to support these two, they are easy to use and implement. If you google this exciting topic you might encounter ancient descriptions of env vars like $CLICOLOR, $CLICOLOR_FORCE. Uh oh. What happens if they are set, but empty? What if $CLICOLOR is set to 0? These sorts of questions are honeypots for ansi warriors, just ignore that crap. I have never encountered anyone in the wild who asked for this or lamented lack of support for $CLICOLOR_FORCE and friends.

  • --color, --no-color, --color=on|off|auto|yes|true|false, --color=yep, etc. - You might need these, really depends on the app. My rule of thumb is that fun, display-oriented apps can skip these flags. Party poopers can always set $NO_COLOR=1 to suck the fun out of the terminal. I'm also not a fan because I can never recall which apps want --color=on and which use --color=true.

Don't forget the golden rule of cli apps, --flags > $env > default (or is_tty).

Luckily, there are excellent libraries to help with this complexity. For example, check out env.go from our friends at Charm.

2. HOW MANY colors (16/256/16M)?

Sometimes referred to as ANSI bit depth or color profile detection.

# of colorsNickWhat?
164 bitThe classic 16 ANSI colors. Black, Red, Green, Yellow, Blue, Magenta, Cyan & White, but wily users will setup "themes" to remap these. Your app has zero control over the actual colors, jokes on you!
256"colorcube"Eventually our ansi overlords decided to expand to 256 colors. This is a huge improvement and you can often build great apps that only use these colors.
16 mil"truecolor"Yes, full RGB in your terminal. Some apps like to crank it up to 11. I recently wrote a terminal app that shows a short movie on the error page. Truecolor required!

I have a whole separate page on color, let's just talk detection. Your app is running, does the terminal support 16, 256 or 16M colors? If you want to really go down the rabbit hole, give terminfo a quick scan, it's only 1,700 lines long.

Or take a look at Ghostty's valiant attempt to stuff a custom entry into terminfo on the fly. Some inside baseball here. When you ssh to a new host, ghostty attempts to update the terminfo database on that machine to teach it a bit about ghostty. This even works sometimes!

In 2026 all popular terminals support at least 256, and the vast majority support 16M. If you can squeeze into 256 colors, just stop here. Be sure to read the colors page, though, because you absolutely must grok (and possibly avoid) the first 16 colors. I digress.

There are still a few exotic laggards like Windows Terminal, GitLab CI, etc. The worst offender was probably Apple Terminal, which added support for truecolor as part of the 2025 Tahoe release. Yes, the cursed Liquid Glass release also upgraded Terminal to support an additional 256*256*255 colors. As usual, don't ask me about Windows unless you want to hear an ignorant rant.

The easiest way to detect 16M is to look for $COLORTERM=truecolor. The terminal will kindly set this for your app to notice. Unfortunately this lovely env variable can easily get lost when using ssh, tmux, etc. The terminal is clearly unchanged, but we've lost $COLORTERM. How does crossterm handle this?

/// This does not always provide a good result.
pub fn available_color_count() -> u16 {
    ...
}

Charm does better, first pouncing on $COLORTERM, then sniffing $TERM for known terminal names, and finally checking the venerable terminfo database as a last, desperate resort.

My take: if you really want full 16M, use libraries that automatically detect and/or downsample to 256. Or you can ignore the problem entirely and wait until someone notices. I actually moved tennis to 256 colors the first time I got a complaint from someone on an intel mac. Nobody noticed this massive change.

3. DARK or LIGHT background?

Does your terminal have a light or dark background? If you decide to venture beyond the first 16 ansi colors, this is a high stakes question. Get it wrong, your app looks awful:

Luckily, this one is real easy. Just put the fd in raw mode, run an OSC 11 query to get the background rgb color, calculate luma to determine "light" vs "dark" and you are all set! There are some minor wrinkles, of course, like not borking the terminal if it doesn't support OSC 11 for some reason. Easy fix - Send a Ps 6 cursor position query, which generates a response in almost all terminals (even the ones that don't support OSC 11). Don't forget to exit raw mode, or the terminal never recovers. You may also want to set a timeout when reading from the raw console (VTIME), though many languages don't expose that API. Whoops.

For best results run this trivial check on /dev/tty.

I implemented this in ruby and I kid you not it's one of the hairiest things I've ever attempted. I ported it to zig in a fit of rage. Don't even get me started on testing or watchexec, ha ha ha ha!

The answer here is to use a library. Also, don't listen to your LLM when it says "this is easy to write yourself".

langlib
gotermbg or lipgloss
javascriptbasiclines/os-theme, maybe?
pythonrich #1170 "... it is impossible"
rubytable_tennis (apologies in advance)
rusttermenv
zigtennis (apologies in advance)

See Also

If you start to google ansi truecolor, you'll find these three guys. TrueColour.md gist, which moved to termstandard/colors, and the kurahaupo gist which is a fork of the first gist. Lots of good info, though I ding all three for failing to notice that the main offender (Apple Terminal) finally added support for truecolor in 2025.