Skip to content
rusthacks.dev
CLI

Colored Terminal Output in Rust With Anstyle and Owo Colors

Add colors to CLI output with anstyle or owo-colors, and turn them off when stdout is not a terminal or NO_COLOR is set, so pipes and logs stay clean.

2 min read
Summarize in AI
Flat illustration for the post “Colored Terminal Output in Rust With Anstyle and Owo Colors”, composed around cli.

Color helps a person find an error in forty lines of output and ruins a log file that captured the escape codes. The trick is not choosing colors but deciding when to print them. This post uses anstyle for the styles and anstream for the decision, the same pair that clap uses internally. It is a polish step in the command line tool series.

anstyle 1, anstream 0.6, Rust 1.90.

Styles and the stream

Cargo.tomlTOML
[dependencies]
anstyle = "1"
anstream = "0.6"
rust
use anstream::println;
use anstyle::{AnsiColor, Style};

fn main() {
    let ok = Style::new().fg_color(Some(AnsiColor::Green.into())).bold();
    let bad = Style::new().fg_color(Some(AnsiColor::Red.into()));

    println!("{ok}passed{ok:#} 12 tests");
    println!("{bad}failed{bad:#} 1 test");
}

{ok} writes the escape sequence to start the style, and {ok:#} writes the reset. Because println! here is from anstream, running cargo run | cat prints plain passed 12 tests. In a terminal you get green bold text. The same program writing with std::println! would put raw escapes into the pipe.

Why not decide yourself

The checks involve several signals:

  1. Is the stream a terminal? (std::io::IsTerminal)
  2. Is NO_COLOR set to a non-empty value? Then disable.
  3. Is CLICOLOR_FORCE set? Then enable even when piped.
  4. Is TERM=dumb? Disable.
  5. Is it Windows? Enable virtual terminal processing.

anstream implements this list. Rolling your own usually gets one of them wrong.

owo-colors for inline use

rust
use owo_colors::OwoColorize;
println!("{}", "error".red().bold());

It reads nicely, but it always emits the codes. Pair it with owo_colors::set_override or a check on IsTerminal, or use its supports-colors feature.

A --color flag

rust
#[derive(clap::ValueEnum, Clone, Copy)]
enum ColorMode { Auto, Always, Never }

Map it to anstream::ColorChoice, and call choice.write_global() at startup. Clap already follows the same environment variables for its own output, so NO_COLOR affects help text without extra code.

Rules of thumb

  • Data on stdout stays plain unless the user asks otherwise.
  • Errors on stderr may be colored, since people read them directly.
  • Never rely on color alone: include words such as error: and warning:.
  • Test with | cat before you ship.

When to skip color

Machine readable formats (JSON, CSV) never get styles. Logging libraries handle their own colors, as in structured logging with tracing. The conventions are documented at no-color.org, and the crate at anstream on docs.rs.

Taggedterminal
Share

Keep reading

Get new posts by email

We send one email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.