10 stable releases (4 major)
| 7.0.0 | Feb 17, 2026 |
|---|---|
| 6.2.0 | Feb 15, 2026 |
| 5.0.0 | Feb 3, 2026 |
| 4.0.0 | Feb 2, 2026 |
| 3.7.0 | Jan 31, 2026 |
#1225 in Command-line interface
Used in 3 crates
(via standout)
11KB
220 lines
Standout
Test your data. Render your view.
Standout is a CLI framework for Rust that enforces separation between logic and presentation. Your handlers return structs, not strings—making CLI logic as testable as any other code.
The Problem
CLI code that mixes logic with println! statements is impossible to unit test:
fn list_command(show_all: bool) {
let todos = storage::list().unwrap();
println!("Your Todos:");
for todo in todos.iter() {
if show_all || todo.status == Status::Pending {
println!(" {} {}", if todo.done { "[x]" } else { "[ ]" }, todo.title);
}
}
}
The Solution
With Standout, handlers return data. The framework handles rendering:
fn list_handler(matches: &ArgMatches, _ctx: &CommandContext) -> HandlerResult<TodoResult> {
let show_all = matches.get_flag("all");
let todos = storage::list()?
.into_iter()
.filter(|t| show_all || t.status == Status::Pending)
.collect();
Ok(Output::Render(TodoResult { todos }))
}
#[test]
fn test_list_filters_completed() {
let result = list_handler(&matches, &ctx).unwrap();
assert!(result.todos.iter().all(|t| t.status == Status::Pending));
}
Because your logic returns a struct, you test the struct. No stdout capture, no regex, no brittleness.
Features
- Testable by design — Handlers return data; framework handles rendering
- Multiple output modes — Rich terminal, JSON, YAML, CSV from the same handler
- MiniJinja templates — Familiar syntax with partials, filters, and hot reload
- CSS/YAML styling — Semantic styles with light/dark mode support
- Tabular layouts — Declarative columns with alignment, truncation, wrapping
- Clap integration — Automatic dispatch via derive macros
- Incremental adoption — Migrate one command at a time
Installation
cargo add standout
Quick Example
use standout::cli::{App, Dispatch, CommandContext, HandlerResult, Output};
use standout::{embed_templates, embed_styles};
#[derive(Subcommand, Dispatch)]
#[dispatch(handlers = handlers)]
pub enum Commands {
List,
}
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());
myapp list # Rich terminal output
myapp list --output json # JSON for scripting
Documentation
You can find comprehensive documentation in our book: standout.magik.works
- Introduction to Standout — Start here
- Rendering System — Templates and styles
- Tabular Layouts — Tables and alignment
- All Topics — Complete reference
Contributing
Contributions welcome. Use the issue tracker for bugs and feature requests.
License
MIT
Dependencies
~140–550KB
~13K SLoC