2 stable releases
| new 1.1.0 | Jan 18, 2026 |
|---|---|
| 1.0.0 | Jan 17, 2026 |
#510 in Template engine
1.5MB
19K
SLoC
Standout
See the main README for full documentation.
lib.rs:
Standout - Non-Interactive CLI Framework
Standout is a CLI output framework that decouples your application logic from terminal presentation. It provides:
- Template rendering with MiniJinja + styled output
- Adaptive themes for named style definitions with light/dark mode support
- Automatic terminal capability detection (TTY, CLICOLOR, etc.)
- Output mode control (Auto/Term/Text/TermDebug)
- Help topics system for extended documentation
- Pager support for long content
This crate is CLI-agnostic at its core - it doesn't care how you parse arguments.
For clap integration, enable the clap feature and see the cli module.
Core Concepts
Theme: Named collection of adaptive styles that respond to light/dark modeColorMode: Light or dark color mode enumOutputMode: Control output formatting (Auto/Term/Text/TermDebug)topics: Help topics system for extended documentation- Style syntax: Tag-based styling
[name]content[/name] Renderer: Pre-compile templates for repeated renderingvalidate_template: Check templates for unknown style tags
Quick Start
use standout::{render, Theme};
use console::Style;
use serde::Serialize;
#[derive(Serialize)]
struct Summary {
title: String,
total: usize,
}
let theme = Theme::new()
.add("title", Style::new().bold())
.add("count", Style::new().cyan());
let template = r#"
[title]{{ title }}[/title]
---------------------------
Total items: [count]{{ total }}[/count]
"#;
let output = render(
template,
&Summary { title: "Report".into(), total: 3 },
&theme,
).unwrap();
println!("{}", output);
Tag-Based Styling
Use tag syntax [name]content[/name] for styling both static and dynamic content:
use standout::{render_with_output, Theme, OutputMode};
use console::Style;
use serde::Serialize;
#[derive(Serialize)]
struct Data { name: String, count: usize }
let theme = Theme::new()
.add("title", Style::new().bold())
.add("count", Style::new().cyan());
let template = r#"[title]Report[/title]: [count]{{ count }}[/count] items by {{ name }}"#;
let output = render_with_output(
template,
&Data { name: "Alice".into(), count: 42 },
&theme,
OutputMode::Text,
).unwrap();
assert_eq!(output, "Report: 42 items by Alice");
Unknown tags show a ? marker in terminal output: [unknown?]text[/unknown?].
Use validate_template to catch typos during development.
Adaptive Themes (Light & Dark)
Themes are inherently adaptive. Individual styles can define mode-specific variations that are automatically selected based on the user's OS color mode.
use standout::Theme;
use console::Style;
let theme = Theme::new()
// Non-adaptive style (same in all modes)
.add("header", Style::new().bold().cyan())
// Adaptive style with light/dark variants
.add_adaptive(
"panel",
Style::new(), // Base
Some(Style::new().fg(console::Color::Black)), // Light mode
Some(Style::new().fg(console::Color::White)), // Dark mode
);
// Rendering automatically detects OS color mode
let output = standout::render(
r#"[panel]active[/panel]"#,
&serde_json::json!({}),
&theme,
).unwrap();
YAML-Based Themes
Themes can also be loaded from YAML files, which is convenient for UI designers who may not be Rust programmers.
use standout::Theme;
let theme = Theme::from_yaml(r#"
header:
fg: cyan
bold: true
panel:
fg: gray
light:
fg: black
dark:
fg: white
title: header
"#).unwrap();
Rendering Strategy
- Build a
Themeusing the fluent builder API or YAML. - Load/define templates using regular MiniJinja syntax (
{{ value }},{% for %}, etc.) with tag-based styling ([name]content[/name]). - Call
renderfor ad-hoc rendering or create aRendererif you have many templates. - Standout processes style tags, auto-detects colors, and returns the final string.
Everything from the theme inward is pure Rust data: no code outside Standout needs to touch stdout/stderr or ANSI escape sequences directly.
More Examples
use standout::{Renderer, Theme};
use console::Style;
use serde::Serialize;
#[derive(Serialize)]
struct Entry { label: String, value: i32 }
let theme = Theme::new()
.add("label", Style::new().bold())
.add("value", Style::new().green());
let mut renderer = Renderer::new(theme).unwrap();
renderer.add_template("row", "[label]{{ label }}[/label]: [value]{{ value }}[/value]").unwrap();
let rendered = renderer.render("row", &Entry { label: "Count".into(), value: 42 }).unwrap();
assert_eq!(rendered, "Count: 42");
Help Topics System
The topics module provides a help topics system for extended documentation:
use standout::topics::{Topic, TopicRegistry, TopicType, render_topic};
// Create and populate a registry
let mut registry = TopicRegistry::new();
registry.add_topic(Topic::new(
"Storage",
"Notes are stored in ~/.notes/\n\nEach note is a separate file.",
TopicType::Text,
Some("storage".to_string()),
));
// Render a topic
if let Some(topic) = registry.get_topic("storage") {
let output = render_topic(topic, None).unwrap();
println!("{}", output);
}
// Load topics from a directory
registry.add_from_directory_if_exists("docs/topics").ok();
Integration with Clap
The cli module (requires clap feature) provides full clap integration with:
- Command dispatch with automatic template rendering
- Help command interception (
help,help <topic>,help topics) - Output flag injection (
--output=auto|term|text|json) - Styled help rendering
use clap::Command;
use standout::cli::{App, HandlerResult, Output};
// Simple parsing with styled help
let matches = App::parse(Command::new("my-app"));
// Full application with command dispatch
App::builder()
.command("list", |_m, _ctx| {
Ok(Output::Render(json!({"items": ["a", "b"]})))
}, "{% for item in items %}{{ item }}\n{% endfor %}")
.build()?
.run(cmd, std::env::args());
Dependencies
~8–26MB
~341K SLoC