#styling #cli-template #color

standout

Styled CLI template rendering with automatic terminal detection

31 stable releases (6 major)

7.6.2 Apr 30, 2026
7.0.0 Feb 17, 2026
6.2.0 Feb 15, 2026
5.0.0 Feb 3, 2026
1.1.0 Jan 18, 2026

#269 in Command-line interface

Download history 21/week @ 2026-01-28 78/week @ 2026-02-04 86/week @ 2026-02-11 1/week @ 2026-02-18 23/week @ 2026-04-15 83/week @ 2026-04-22 46/week @ 2026-04-29

152 downloads per month
Used in 4 crates

MIT license

2MB
32K SLoC

Standout

A CLI framework for Rust that enforces separation between logic and presentation.

Test your data. Render your view.

use standout::cli::{App, HandlerResult, Output, CommandContext};
use clap::ArgMatches;
use serde::Serialize;

#[derive(Serialize)]
struct ListResult { items: Vec<String>, total: usize }

fn list_handler(_m: &ArgMatches, _ctx: &CommandContext) -> HandlerResult<ListResult> {
    let items = storage::list()?;
    Ok(Output::Render(ListResult { total: items.len(), items }))
}

// Test the handler directly—no stdout capture needed
#[test]
fn test_list() {
    let result = list_handler(&matches, &ctx).unwrap();
    assert_eq!(result.total, 3);
}

What is Standout?

Standout combines two standalone libraries into a cohesive framework:

  • standout-dispatch — Execution pattern where handlers return data, renderers produce output
  • standout-render — Terminal rendering with templates, themes, and adaptive styles

The framework provides the glue: clap integration, --output flag handling, auto-dispatch from derive macros, and the AppBuilder configuration API.

Why Standout?

CLI code that mixes logic with println! is impossible to unit test. With Standout:

  • Handlers return structs, not strings—test them like any other function
  • Multiple output modes from the same handler: rich terminal, JSON, YAML, CSV
  • MiniJinja templates with hot reload during development
  • CSS/YAML themes with automatic light/dark mode support
  • Incremental adoption—migrate one command at a time

Quick Start

[dependencies]
standout = "2.1"
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
use standout::cli::{App, Dispatch, CommandContext, HandlerResult, Output};
use standout::{embed_templates, embed_styles};
use clap::Subcommand;

#[derive(Subcommand, Dispatch)]
#[dispatch(handlers = handlers)]
pub enum Commands {
    List,
}

mod handlers {
    pub fn list(_m: &ArgMatches, _ctx: &CommandContext) -> HandlerResult<Vec<String>> {
        Ok(Output::Render(vec!["item-1".into(), "item-2".into()]))
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = App::builder()
        .commands(Commands::dispatch_config())
        .templates(embed_templates!("src/templates"))
        .styles(embed_styles!("src/styles"))
        .build()?;

    app.run(Cli::command(), std::env::args());
    Ok(())
}
myapp list                  # Rich terminal output
myapp list --output json    # JSON for scripting

Documentation

Framework Topics

Crate Documentation

API Reference

Standalone Crates

Each component can be used independently:

License

MIT

Dependencies

~11–32MB
~438K SLoC