· 5 min read ·

Building at Terminal Velocity: Three Years in Rust's TUI Ecosystem

Source: lobsters

When Orhun Parmaksız reflected on reaching 800 Rust projects in three years on his blog, the number carried two readings at once. As a personal productivity story it’s striking; as ecosystem evidence, it points at something more interesting about Rust and terminal tooling finding an unusually tight fit.

Orhun (GitHub: orhun) is the maintainer of ratatui, the community-maintained fork of tui-rs that became the de facto standard for building terminal user interfaces in Rust after the original author archived the repository in 2022. He’s also the author of git-cliff, a changelog generator that parses conventional commits; binsider, an ELF binary analyzer with a TUI; systeroid, a sysctl interface; gpg-tui, a GPG key management interface; oha, an HTTP load testing tool; and dozens of others. Across all of it, the throughput is genuine.

What the Terminal Offers

The terminal is a constrained environment, but those constraints are load-bearing. You work with a coordinate grid of character cells, a set of ANSI escape sequences for color and cursor control, and a keyboard event stream. Within those limits, you can build something that feels precise and fast in a way that Electron-wrapped webviews rarely do.

Rust fits this environment well, for reasons that go beyond raw performance. The absence of a garbage collector matters specifically during frame rendering: a 16ms budget at 60fps is tight enough that a mid-frame collection would be visible as a stutter. Static binaries mean you ship one file with no runtime dependencies, which matters for tooling that developers want to install quickly and trust to be present on any machine. The type system catches a class of UI state bugs at compile time that you’d otherwise discover through user reports.

The Stack Underneath

ratatui is the clearest example of the Rust terminal stack becoming something coherent and buildable-on. It uses crossterm as its default backend, which abstracts over platform differences in terminal APIs across Linux, macOS, and Windows. The API is immediate-mode: you provide a closure that receives a Frame, render widgets into it, and the library handles diffing and flushing to the terminal.

use ratatui::{
    backend::CrosstermBackend,
    layout::{Constraint, Direction, Layout},
    widgets::{Block, Borders, Paragraph},
    Terminal,
};
use std::io;

fn main() -> io::Result<()> {
    let backend = CrosstermBackend::new(io::stdout());
    let mut terminal = Terminal::new(backend)?;

    terminal.draw(|frame| {
        let layout = Layout::default()
            .direction(Direction::Vertical)
            .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
            .split(frame.size());

        frame.render_widget(
            Paragraph::new("Top pane")
                .block(Block::default().borders(Borders::ALL).title("Section A")),
            layout[0],
        );
        frame.render_widget(
            Paragraph::new("Bottom pane")
                .block(Block::default().borders(Borders::ALL).title("Section B")),
            layout[1],
        );
    })?;

    Ok(())
}

The layout system uses Constraint-based sizing, letting you express UI structure as ratios, fixed lengths, min/max bounds, or fill. Built-in widgets include List, Table, Gauge, Chart, Sparkline, Canvas, and Tabs. The surface area is manageable enough that a competent Rust developer can go from zero to a usable TUI in an afternoon, which matters when you’re trying to build at pace.

ratatui’s widget trait is designed for composability. Custom widgets implement Widget or StatefulWidget, and the rendering pipeline is explicit rather than magical. There’s no retained state, no implicit diffing tree. You describe what the frame should look like, and the library makes it so.

Comparison With Other Ecosystems

Other languages have reached for the same space with different architectural choices. Go has Bubble Tea from Charmbracelet, which uses an Elm-inspired architecture: a model, a message type, an update function, and a view function. Charmbracelet built a compelling suite around it including Lip Gloss for styling and Bubbles for pre-built components. That stack produced Lazygit, K9s, and Slides.

Python has Rich and Textual, both from Textualize. Textual is ambitious, bringing CSS-style layout and event-driven architecture to the terminal. The development experience is high-level enough to attract developers who wouldn’t otherwise write TUI software, at the cost of startup latency and the Python runtime requirement.

The C tradition runs through ncurses, which underlies htop, vim, and Midnight Commander. ncurses is reliable and universal, but it writes the complexity directly onto the developer, particularly around cleanup, signal handling, and platform quirks.

What Rust offers is a combination of those trade-offs without most of their costs: the performance profile closer to C, the cross-platform story of Go, and a library ecosystem growing fast enough to have answers for most requirements. The crates.io registry makes dependency management consistent in a way that C’s header-hunting and library-linking never was.

The Other Side of 800

The number that doesn’t appear in productivity write-ups is the maintenance number. Eight hundred repositories means eight hundred places where issues can be opened, dependencies can go stale, and build failures can accumulate from upstream changes. Rust’s edition system and stability guarantees help: a crate that compiled on Rust 1.60 will still compile on Rust 1.80. Dependency updates are more contained than in JavaScript. But the aggregate surface area is still real.

The broader Rust CLI trend carries this dynamic throughout the ecosystem. Tools like ripgrep, bat, eza, fd, bottom, and delta attracted significant user bases quickly, then faced the familiar open source question of who maintains them when the original author’s time constraints change. eza is notable here: it’s a community fork of exa, which was abandoned by its original author. The fork preserved the project and kept it shipping, which is essentially the same story as ratatui inheriting tui-rs.

Orhun has been on both sides of this dynamic. ratatui under his stewardship became more actively developed than the original, adding features, improving documentation, and building a contributor community. That outcome is not inevitable. It requires someone to show up consistently, triage issues, review pull requests, and keep the build green across Rust editions.

What the Ecosystem Earned

The Rust terminal ecosystem today has a coherent answer for most terminal application requirements. The building blocks exist, they compose well, and they’re maintained by people who use them. Many ecosystems have fragmented libraries that technically work but don’t fit together, or libraries that are capable but lack the documentation and examples needed to build on them quickly. The Rust terminal space has largely avoided that fragmentation.

What orhun’s output represents, beyond personal productivity, is proof that this ecosystem is buildable-on at high velocity. The bottleneck in Rust terminal development is no longer the language or the library infrastructure. The ratatui showcase lists dozens of applications built by independent authors: music players, database explorers, Kubernetes dashboards, file managers, API clients, and more. The variety there reflects genuine productivity across multiple independent developers, not just one person’s unusual work rate.

Eight hundred projects is a striking milestone, but the more durable signal is that the conditions making it possible are available to anyone working in this space.

Was this interesting?