
Beautiful terminal output for Rust, inspired by Python's Rich.
cargo add rich_rust
Or with all features: cargo add rich_rust --features full
See rich_rust in action with the Nebula Deploy demo — a complete showcase of terminal UI capabilities wrapped in a fictional deployment narrative.
# Full demo with all features (recommended)
cargo run --bin demo_showcase --features showcase
# Quick mode for faster run
cargo run --bin demo_showcase --features showcase -- --quick
# CI-safe mode (non-blocking, deterministic output)
cargo run --bin demo_showcase --features showcase -- --quick --no-live --no-interactive
# List available scenes
cargo run --bin demo_showcase --features showcase -- --list-scenes
# Run a specific scene
cargo run --bin demo_showcase --features showcase -- --scene hero
What the demo showcases: - Typography — styled text, colors, bold/italic/underline, themes - Tables — alignment, borders, headers, badges, ASCII fallback - Panels — box styles, titles, padding, nested layouts - Trees — hierarchical data, custom guides, icons - Progress — bars, spinners, live updates - Syntax — code highlighting for 100+ languages (Rust, YAML, TOML, etc.) - Markdown — CommonMark + GFM rendering - JSON — pretty-printed, theme-aware output - Tracing — structured logging integration - Export — HTML/SVG capture of terminal output
Building beautiful terminal UIs in Rust is tedious. You either: - Write raw ANSI escape codes (error-prone, unreadable) - Use low-level crates that require boilerplate for simple things - Miss features like automatic terminal capability detection, tables, progress bars
rich_rust brings Python Rich's ergonomic API to Rust: styled text, tables, panels, progress bars, syntax highlighting, and more. Zero unsafe code, automatic terminal detection.
| Feature | rich_rust | Raw ANSI | colored | termion |
|---|---|---|---|---|
Markup syntax ([bold red]text[/]) |
Yes | No | No | No |
| Tables with auto-sizing | Yes | No | No | No |
| Panels and boxes | Yes | No | No | No |
| Progress bars & spinners | Yes | No | No | No |
| Syntax highlighting | Yes | No | No | No |
| Markdown rendering | Yes | No | No | No |
| Auto color downgrade | Yes | No | Partial | No |
| Unicode width handling | Yes | No | No | Partial |
use rich_rust::prelude::*;
fn main() {
let console = Console::new();
// Styled text with markup
console.print("[bold green]Success![/] Operation completed.");
console.print("[red on white]Error:[/] [italic]File not found[/]");
// Horizontal rule
console.rule(Some("Configuration"));
// Tables
let mut table = Table::new()
.title("Users")
.with_column(Column::new("Name"))
.with_column(Column::new("Role").justify(JustifyMethod::Right));
table.add_row_cells(["Alice", "Admin"]);
table.add_row_cells(["Bob", "User"]);
console.print_renderable(&table);
// Panels
let panel = Panel::from_text("Hello, World!")
.title("Greeting")
.width(40);
console.print_renderable(&panel);
}
Output:
Success! Operation completed.
Error: File not found
─────────────────── Configuration ───────────────────
┌─────────────────────── Users ───────────────────────┐
│ Name │ Role │
├────────┼────────┤
│ Alice │ Admin │
│ Bob │ User │
└────────────────────────────────────────────────────┘
┌─────────── Greeting ───────────┐
│ Hello, World! │
└────────────────────────────────┘
#![forbid(unsafe_code)]
The entire codebase uses safe Rust. No segfaults, no data races, no undefined behavior.
API and behavior closely follow Python Rich. If you know Rich, you know rich_rust. The RICH_SPEC.md documents every behavioral detail.
Instead of Python's duck typing, rich_rust uses explicit render methods and an optional measurement trait:
use rich_rust::console::{Console, ConsoleOptions};
use rich_rust::measure::{Measurement, RichMeasure};
use rich_rust::segment::Segment;
struct MyRenderable;
impl MyRenderable {
fn render(&self, width: usize) -> Vec<Segment> {
vec![Segment::plain(format!("width={width}"))]
}
}
impl RichMeasure for MyRenderable {
fn rich_measure(&self, _console: &Console, _options: &ConsoleOptions) -> Measurement {
Measurement::exact(10)
}
}
Renderables expose render(...) -> Vec<Segment>. Implement RichMeasure to
participate in layout width calculations.
rich_rust detects terminal capabilities at runtime: - Color support (4-bit, 8-bit, 24-bit truecolor) - Terminal dimensions - Unicode support - Legacy Windows console
Colors automatically downgrade to what the terminal supports.
Core functionality has few dependencies. Optional features (syntax highlighting, markdown, JSON, tracing) are behind feature flags to keep compile times fast.
| Feature | rich_rust | Python Rich | colored | termcolor | owo-colors |
|---|---|---|---|---|---|
| Language | Rust | Python | Rust | Rust | Rust |
| Markup parsing | [bold]text[/] |
[bold]text[/] |
No | No | No |
| Tables | Yes | Yes | No | No | No |
| Panels/Boxes | Yes | Yes | No | No | No |
| Progress bars | Yes | Yes | No | No | No |
| Trees | Yes | Yes | No | No | No |
| Syntax highlighting | Yes (syntect) | Yes (Pygments) | No | No | No |
| Markdown | Yes | Yes | Yes | No | No |
| JSON pretty-print | Yes | Yes | No | No | No |
| Color downgrade | Auto | Auto | Partial | Yes | No |
| Zero unsafe | Yes | N/A | Yes | Yes | Yes |
| No runtime | Yes | No (Python) | Yes | Yes | Yes |
| Single binary | Yes | No | Yes | Yes | Yes |
When to use rich_rust: - You want Python Rich's features in Rust - You need tables, panels, or progress bars - You want markup syntax for styling - You're building CLI tools that need beautiful output
When to use alternatives:
- colored: Simple color-only needs, minimal dependencies
- termcolor: Cross-platform color with Windows support
- owo-colors: Zero-allocation, const colors
- Python Rich: You're writing Python
cargo add rich_rust
# Syntax highlighting
cargo add rich_rust --features syntax
# Markdown rendering
cargo add rich_rust --features markdown
# JSON pretty-printing
cargo add rich_rust --features json
# Tracing integration
cargo add rich_rust --features tracing
# All features
cargo add rich_rust --features full
git clone https://github.com/Dicklesworthstone/rich_rust
cd rich_rust
cargo build --release
[dependencies]
rich_rust = "0.1"
# Or with features:
rich_rust = { version = "0.1", features = ["full"] }
use rich_rust::prelude::*;
let console = Console::new();
// Using markup syntax
console.print("[bold]Bold[/] and [italic red]italic red[/]");
// Using explicit style
console.print_styled("Styled text", Style::new().bold().underline());
// Plain text (no markup parsing)
console.print_plain("[brackets] are literal here");
let mut table = Table::new()
.title("Data")
.with_column(Column::new("Key"))
.with_column(Column::new("Value").justify(JustifyMethod::Right));
table.add_row_cells(["version", "1.0.0"]);
table.add_row_cells(["status", "active"]);
console.print_renderable(&table);
let panel = Panel::from_text("Important message here")
.title("Notice")
.subtitle("v1.0")
.width(50);
console.print_renderable(&panel);
// Simple rule
console.rule(None);
// Rule with title
console.rule(Some("Section"));
// Styled rule
let rule = Rule::with_title("Custom")
.style(Style::parse("cyan bold").unwrap_or_default())
.align_left();
console.print_renderable(&rule);
| Markup | Effect |
|---|---|
[bold]text[/] |
Bold text |
[italic]text[/] |
Italic text |
[underline]text[/] |
Underlined text |
[red]text[/] |
Red foreground |
[on blue]text[/] |
Blue background |
[bold red on white]text[/] |
Combined styles |
[#ff0000]text[/] |
Hex color |
[rgb(255,0,0)]text[/] |
RGB color |
[color(196)]text[/] |
256-color palette |
Python Rich defines many named styles (e.g. rule.line, table.header). rich_rust
ports this theme system and lets you add custom names:
use rich_rust::prelude::*;
let theme = Theme::from_style_definitions([("warning", "bold red")], true).unwrap();
let console = Console::builder().theme(theme).build();
console.print("[warning]Danger[/]");
Style::new()
.bold()
.italic()
.underline()
.strikethrough()
.dim()
.reverse()
.foreground(Color::parse("red").unwrap())
.background(Color::parse("white").unwrap())
| System | Colors | Detection |
|---|---|---|
| Standard | 16 | Basic terminals |
| 256-color | 256 | Most modern terminals |
| Truecolor | 16M | iTerm2, Windows Terminal, etc. |
Panel::from_text("content").rounded() // ╭─╮ (default)
Panel::from_text("content").square() // ┌─┐
Panel::from_text("content").heavy() // ┏━┓
Panel::from_text("content").double() // ╔═╗
Panel::from_text("content").ascii() // +-+
let bar = ProgressBar::new()
.completed(75)
.total(100)
.width(40);
console.print_renderable(&bar);
let mut root = TreeNode::new("Root");
root.add_child(TreeNode::new("Child 1"));
root.add_child(TreeNode::new("Child 2"));
let tree = Tree::new(root);
console.print_renderable(&tree);
use rich_rust::prelude::*;
fn main() -> std::io::Result<()> {
let console = Console::new().shared();
let live = Live::new(console.clone()).renderable(Text::new("Loading..."));
live.start(true)?;
live.update(Text::new("Done!"), true);
live.stop()?;
Ok(())
}
For external writers, use live.stdout_proxy() / live.stderr_proxy() to route output
through the Live display.
use rich_rust::prelude::*;
let mut layout = Layout::new().name("root");
layout.split_column(vec![
Layout::new()
.name("header")
.size(3)
.renderable(Panel::from_text("Header")),
Layout::new().name("body").ratio(1),
]);
if let Some(body) = layout.get_mut("body") {
body.split_row(vec![
Layout::new().name("left").ratio(1).renderable(Panel::from_text("Left")),
Layout::new().name("right").ratio(2).renderable(Panel::from_text("Right")),
]);
}
console.print_renderable(&layout);
use rich_rust::prelude::*;
use log::LevelFilter;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let console = Console::new().shared();
RichLogger::new(console)
.level(LevelFilter::Info)
.show_path(true)
.init()?;
log::info!("Server started");
Ok(())
}
Note: log macros come from the log crate; add log = "0.4" to your Cargo.toml.
Tracing: enable rich_rust feature tracing and install RichTracingLayer if you use
the tracing ecosystem.
Export terminal output to shareable files:
use rich_rust::prelude::*;
let mut console = Console::new();
console.begin_capture();
console.print("[bold green]Hello[/]");
let html = console.export_html(false); // false = don't clear buffer
let svg = console.export_svg(true); // true = clear buffer after
Note: The HTML/SVG exports follow Python Rich's export templates (including optional
terminal-window chrome). SVG is rendered with SVG primitives (<text>, <rect>, clip paths),
so it works in browsers and in many SVG-capable viewers (no <foreignObject> required).
For a quick demo of export capabilities, run:
cargo run --bin demo_showcase --features showcase -- --export
syntax feature)use rich_rust::prelude::*;
let code = r#"fn main() { println!("Hello"); }"#;
let syntax = Syntax::new(code, "rust")
.line_numbers(true)
.theme("Solarized (dark)");
console.print_renderable(&syntax);
markdown feature)use rich_rust::prelude::*;
let md = Markdown::new("# Header\n\nParagraph with **bold**.");
console.print_renderable(&md);
Rust doesn't have Python
$ claude mcp add rich_rust \
-- python -m otcore.mcp_server <graph>