Thanks to visit codestin.com
Credit goes to lib.rs

2 stable releases

new 1.1.0 Jan 18, 2026
1.0.0 Jan 17, 2026

#510 in Template engine

MIT license

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 mode
  • ColorMode: Light or dark color mode enum
  • OutputMode: 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 rendering
  • validate_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

  1. Build a Theme using the fluent builder API or YAML.
  2. Load/define templates using regular MiniJinja syntax ({{ value }}, {% for %}, etc.) with tag-based styling ([name]content[/name]).
  3. Call render for ad-hoc rendering or create a Renderer if you have many templates.
  4. 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