{"slug": "building-ratatop-a-rust-system-monitor-with-ratatui", "title": "Building ratatop: a Rust System Monitor with ratatui", "summary": "Developer Maneshwar built ratatop, a Linux system monitor using the Rust ratatui library, as a learning project to explore immediate-mode terminal user interfaces. The project contrasts with the developer's previous Bubble Tea-based TUI app peektea, highlighting ratatui's library approach versus Bubble Tea's framework architecture.", "body_md": "*Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. Star git-lrc to help devs discover the project. Do give it a try and share your feedback.*\n\nFor a long while now I've been trying to learn how to build TUI apps.\n\nLast month I made peektea, a keyboard-driven terminal file browser. The whole journey is written up here: ** the peektea series**.\n\nA minimal terminal file browser built with [Bubble Tea](https://github.com/charmbracelet/bubbletea).\n\nPeek through your filesystem with arrow keys (or vim keys), then pour each file straight into the app you've configured for it.\n\nA quick peek before you steep\nClick below to watch the demo on YouTube (7m), or scroll down to see gif (10s)\n[\ndemo in gif version\n](https://www.youtube.com/watch?v=yDNN5x9Y9Ok)\n\n**One-liner:**\n\n```\ncurl -fsSL https://raw.githubusercontent.com/lovestaco/peektea/master/scripts/install.sh | sh\n```\n\n**Download a binary** (no Go required) — grab the latest release for your platform from the [releases page](https://github.com/lovestaco/peektea/releases/latest):\n\n| Platform | File |\n|---|---|\n| Linux x86-64 | `peektea_*_linux_amd64.tar.gz` |\n| Linux arm64 | `peektea_*_linux_arm64.tar.gz` |\n| macOS x86-64 | `peektea_*_darwin_amd64.tar.gz` |\n| macOS Apple Silicon | `peektea_*_darwin_arm64.tar.gz` |\n\nExtract and put the `peektea`\n\nbinary anywhere on your `$PATH`\n\n.\n\n**Install with Go:**\n\n```\ngo install github.com/lovestaco/peektea@latest\n```\n\n**Build from source:**\n\n```\ngit clone https://github.com/lovestaco/peektea\ncd peektea\nmake install\n```\n\n`make install`\n\nputs the binary in `~/go/bin`\n\nand figures out `$PATH`\n\nfor you:\n\n`~/.local/bin`\n\nis on…That one was [Bubble Tea](https://github.com/charmbracelet/bubbletea) with Go.\n\nSo I got curious about what the other side looks like.\n\nWhat can Rust do here?\n\nWhich is how I landed on [ratatui](https://ratatui.rs).\n\nI'm connected to [Orhun Parmaksız](https://github.com/orhun) on LinkedIn, I keep seeing his posts about it on my feed, and eventually curiosity won.\n\nSo here we are.\n\nI built a basic Linux visual monitor to learn the library:\n\nFor the next few days I'll be poking at this and writing down whatever I learn. This is post one.\n\nratatui is a Rust library for building terminal user interfaces.\n\nIt grew out of `tui-rs`\n\n, which went unmaintained back in 2023, and a group of folks picked it up and ran with it.\n\nOrhun is one of the maintainers.\n\nThe name is a [Ratatouille](https://en.wikipedia.org/wiki/Ratatouille_(film)) joke, and honestly the film's whole \"anyone can cook\" thing turned out to be the right energy for a first project.\n\nYou do not need to be a Rust wizard to get something on screen.\n\nOne thing to get straight early: ratatui is a **library**, not a framework.\n\nBubble Tea hands you an architecture.\n\nYou fill in `Model`\n\n, `Update`\n\n, `View`\n\n, and it runs the show.\n\nratatui hands you a drawing surface and says good luck.\n\nThat difference is the whole post, really.\n\nBubble Tea is built on the [Elm Architecture](https://guide.elm-lang.org/architecture/).\n\nState goes in, messages come back, you return a new model, the framework re-renders.\n\nIt is retained and it is opinionated.\n\nratatui is **immediate mode**.\n\nThere is no widget tree living in memory between frames.\n\nThere is no `View()`\n\nthat the framework calls for you.\n\nEvery single frame, you build the widgets from scratch, draw them, and throw them away.\n\nComing from Bubble Tea this felt wrong for about a day.\n\nWhere does the state live? Wherever you want. Who calls the render? You do. When do widgets get cleaned up? They were never alive.\n\nThen it clicked, and it felt great.\n\nNo lifecycle, no diffing your own state, no \"why did this component not re-render\".\n\nYou want something on screen, you draw it.\n\nEvery frame.\n\nThe catch is that \"you own the loop\" also means you own everything else.\n\nQuitting, resizing, timing, keyboard input, all of it is yours now.\n\nHere is genuinely the whole shape of a ratatui program:\n\n``` php\nfn main() -> std::io::Result<()> {\n    let mut terminal = ratatui::init();   // alt screen + raw mode\n    let result = run(&mut terminal);\n    ratatui::restore();                   // put the terminal back\n    result\n}\n\nfn run(terminal: &mut DefaultTerminal) -> std::io::Result<()> {\n    loop {\n        terminal.draw(|frame| {\n            let hello = Paragraph::new(\"hello from the kitchen\");\n            frame.render_widget(hello, frame.area());\n        })?;\n\n        if event::poll(Duration::from_millis(100))? {\n            if let Some(key) = event::read()?.as_key_press_event() {\n                if key.code == KeyCode::Char('q') {\n                    return Ok(());\n                }\n            }\n        }\n    }\n}\n```\n\n`ratatui::init()`\n\nis the modern convenience wrapper.\n\nIt flips on raw mode and switches to the alternate screen buffer, the same trick `vim`\n\nand `htop`\n\nuse so your shell history stays clean when you quit.\n\nIn peektea that was `tea.WithAltScreen()`\n\n.\n\nSame idea, different kitchen.\n\n`ratatui::restore()`\n\nputs everything back.\n\nWorth wiring this into a panic hook too, because a Rust panic inside raw mode leaves your terminal in a genuinely cursed state.\n\nInput is not ratatui's job at all.\n\nThat comes from [crossterm](https://github.com/crossterm-rs/crossterm), which ratatui re-exports as `ratatui::crossterm`\n\nso your versions cannot drift apart.\n\n`event::poll`\n\nwith a timeout is what keeps the loop from blocking, so you can redraw on a timer instead of only on keypress.\n\nThe core layout idea is small: you have a `Rect`\n\n, and you cut it into smaller `Rect`\n\ns.\n\n```\nlet [header, body, footer] = Layout::vertical([\n    Constraint::Length(1),   // exactly one row\n    Constraint::Min(0),      // whatever is left over\n    Constraint::Length(1),   // exactly one row\n])\n.areas(area);\n```\n\nThat `.areas()`\n\nreturning a fixed-size array is lovely.\n\nThe compiler checks that you destructured exactly three areas from three constraints.\n\nGet it wrong and it does not build.\n\nThe constraint types cover most of what you want:\n\n`Length(n)`\n\nfor exact cells`Min(n)`\n\nand `Max(n)`\n\nfor flexible with a bound`Percentage(n)`\n\nand `Ratio(a, b)`\n\nfor proportional`Fill(weight)`\n\nfor splitting leftovers by weightThere is also `.spacing(n)`\n\nto put gaps between chunks, and `.flex(Flex::Center)`\n\nto control where slack goes when your constraints do not fill the space.\n\nI used `Flex::Center`\n\nto centre a box, then swapped it to `Flex::Start`\n\nto pin it to the top of the terminal.\n\nOne enum, done.\n\nNesting is just calling `Layout`\n\nagain on one of the pieces you got back.\n\nThat is the whole layout system.\n\nA widget in ratatui is a plain value that gets eaten when you draw it.\n\nThe trait is literally:\n\n```\npub trait Widget {\n    fn render(self, area: Rect, buf: &mut Buffer);\n}\n```\n\nNote `self`\n\n, not `&self`\n\n. It is consumed.\n\nYou are not holding onto it.\n\nStock widgets get you surprisingly far.\n\n`Block`\n\nis the bordered container, and it composes nicely:\n\n``` js\nlet block = Block::default()\n    .borders(Borders::ALL)\n    .border_type(BorderType::Rounded)     // ╭─╮ instead of ┌─┐\n    .title(\"CPU\")\n    .title_bottom(Line::from(\"q to quit\").right_aligned());\n\nlet inner = block.inner(area);   // the area inside the borders\nblock.render(area, buf);\n```\n\n`block.inner(area)`\n\nis the one to remember.\n\nIt gives you the space inside the border so your content does not get eaten by the frame.\n\nFor text, the hierarchy is `Span`\n\n(styled string) inside `Line`\n\n(a row) inside `Text`\n\n(many rows):\n\n```\nLine::from(vec![\n    Span::styled(\"C\", Style::default().fg(Color::Red)),\n    Span::styled(\"PU\", Style::default().fg(Color::White)),\n])\n```\n\nThere is also a `Stylize`\n\ntrait that makes this much less shouty, giving you `\"CPU\".red().bold()`\n\ndirectly on string literals.\n\nVery nice for quick work.\n\nThis is where it got fun.\n\nThe look I was going after uses [braille](https://en.wikipedia.org/wiki/Braille_Patterns) characters to fit more resolution into a terminal cell.\n\nEach braille glyph is a 2x4 grid of dots, so one character cell holds 8 addressable points instead of 1.\n\nNo stock widget does that. So you write one. You implement `Widget`\n\n, you get a `Buffer`\n\n, and you set cells directly.\n\n```\nimpl Widget for MyGraph<'_> {\n    fn render(self, area: Rect, buf: &mut Buffer) {\n        let cell = &mut buf[(x, y)];\n        cell.set_char('⣿');\n        cell.set_style(Style::default().fg(Color::Green));\n    }\n}\n```\n\n`Buffer`\n\nis a flat grid of `Cell`\n\ns, where each cell holds a symbol plus its style. That is the entire drawing surface.\n\nOnce you realise a custom widget is just \"write characters into a grid\", the intimidation factor drops off a cliff.\n\nThen I spent an embarrassing amount of time on the fact that braille dot numbering is not sequential.\n\nThe bits go 1, 2, 3 down the left column, 4, 5, 6 down the right, and then 7 and 8 got bolted on later as a fourth row.\n\nSo the bitmask is `0x01, 0x02, 0x04, 0x40`\n\non the left and `0x08, 0x10, 0x20, 0x80`\n\non the right.\n\nThat `0x40`\n\nsitting where `0x08`\n\nshould be is a 19th century design decision reaching out to ruin your afternoon.\n\nWorth it though.\n\nThe payoff is graphs that look like graphs instead of blocks.\n\nWhile drawing per-core graphs, I decided that any non-zero reading should light at least one dot.\n\nMy reasoning: an idle core showing literally nothing looks broken.\n\nReasonable. Also completely wrong.\n\nThe aggregate CPU line is never zero.\n\nSo every single column got exactly one dot, and my beautiful history graph rendered as a perfectly flat line, all the way across, forever.\n\nI had built a very expensive way to draw a dash.\n\nThe fix was to delete my clever bit and let low values round down to blank.\n\nThose gaps are the entire texture.\n\nLetting values fall to nothing is what makes it look alive.\n\nLast piece, because it explains a debugging trap I fell into.\n\nratatui keeps two buffers and only emits the cells that actually changed.\n\nThat is why redrawing everything every frame is not wasteful.\n\nThe trap: I tried to test a feature by piping the app's output and grepping for a string I expected on screen.\n\nFound nothing, assumed the feature was broken. It was not.\n\nBecause of the diffing, a changed value never appears as a contiguous string in the output stream. It arrives as a few individual cells with cursor moves between them.\n\nLesson: test your state and your widgets, not the escape codes.\n\nIf you've been on the fence about TUIs, ratatui is a genuinely nice place to start.\n\nThe API is small, the docs are good, and the immediate mode model means there is very little machinery hiding from you.\n\nCode is here if you want a look: **lovestaco/ratatop**\n\nA reimplementation of [btop](https://github.com/aristocratos/btop) in Rust, built on\n[ratatui](https://ratatui.rs).\n\nThe goal is a resource monitor that feels like btop: the four-box layout, braille graphs with gradient fills, mouse-driven navigation, and a themable process manager, but written as a modern Rust TUI.\n\n**The CPU box works.** It samples real data and renders btop's layout:\n\nPer-core braille history graphs, a gradient load bar with sub-cell resolution frequency, package temperature, battery, and load averages.\n\nEverything else, the memory, network, and process boxes, is still to come.\n\n`/proc`\n\nand `/sys`\n\non Linux first, read directly rather than\nshelling out. btop's `src/linux/btop_collect.cpp`\n\nis the reference for what to\nread and how often; the BSD/macOS collectors are out of scope for v1.More soon. Anyone can cook.\n\nAI agents write code fast. They also silently remove logic, change behavior, and introduce bugs without telling you. You often find out in production.\n\ngit-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.\n\nAny feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.\n\n⭐ Star it on GitHub:\n\n| [🇩🇰 Dansk](https://github.com/HexmosTech/git-lrc/readme/README.da.md) | [🇪🇸 Español](https://github.com/HexmosTech/git-lrc/readme/README.es.md) | [🇮🇷 Farsi](https://github.com/HexmosTech/git-lrc/readme/README.fa.md) | [🇫🇮 Suomi](https://github.com/HexmosTech/git-lrc/readme/README.fi.md) | [🇯🇵 日本語](https://github.com/HexmosTech/git-lrc/readme/README.ja.md) | [🇳🇴 Norsk](https://github.com/HexmosTech/git-lrc/readme/README.nn.md) | [🇵🇹 Português](https://github.com/HexmosTech/git-lrc/readme/README.pt.md) | [🇷🇺 Русский](https://github.com/HexmosTech/git-lrc/readme/README.ru.md) | [🇦🇱 Shqip](https://github.com/HexmosTech/git-lrc/readme/README.sq.md) | [🇨🇳 中文](https://github.com/HexmosTech/git-lrc/readme/README.zh.md) | [🇮🇳 हिन्दी](https://github.com/HexmosTech/git-lrc/readme/README.hi.md) |\n\nGenAI today is a **race car without brakes**. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents *silently break things*: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.\n\n** git-lrc is your braking system.** It hooks into\n\n`git commit`\n\nand runs an AI review on every diff In short, git-lrc helps **Prevent Outages, Breaches, and Technical Debt Before They Happen**\n\n**At a glance:** [10 risk categories](https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for) · [100+ failure patterns tracked](https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for) · every commit…\n\nAI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.\n\ngit-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.\n\nAny feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.\n\n⭐ Star it on GitHub:\n\n| [🇩🇰 Dansk](https://github.com/HexmosTech/git-lrc/readme/README.da.md) | [🇪🇸 Español](https://github.com/HexmosTech/git-lrc/readme/README.es.md) | [🇮🇷 Farsi](https://github.com/HexmosTech/git-lrc/readme/README.fa.md) | [🇫🇮 Suomi](https://github.com/HexmosTech/git-lrc/readme/README.fi.md) | [🇯🇵 日本語](https://github.com/HexmosTech/git-lrc/readme/README.ja.md) | [🇳🇴 Norsk](https://github.com/HexmosTech/git-lrc/readme/README.nn.md) | [🇵🇹 Português](https://github.com/HexmosTech/git-lrc/readme/README.pt.md) | [🇷🇺 Русский](https://github.com/HexmosTech/git-lrc/readme/README.ru.md) | [🇦🇱 Shqip](https://github.com/HexmosTech/git-lrc/readme/README.sq.md) | [🇨🇳 中文](https://github.com/HexmosTech/git-lrc/readme/README.zh.md) | [🇮🇳 हिन्दी](https://github.com/HexmosTech/git-lrc/readme/README.hi.md) |\n\nGenAI today is a **race car without brakes**. It accelerates fast -- you describe something, and large blocks of code appear instantly. But AI agents *silently break things*: they remove logic, relax constraints, introduce expensive cloud calls, leak credentials, and change behavior -- without telling you. You often find out in production.\n\n** git-lrc is your braking system.** It hooks into\n\n`git commit`\n\nand runs an AI review on every diff In short, git-lrc helps **Prevent Outages, Breaches, and Technical Debt Before They Happen**\n\n**At a glance:** [10 risk categories](https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for) · [100+ failure patterns tracked](https://github.com/HexmosTech/git-lrc#what-git-lrc-checks-for) · every commit…", "url": "https://wpnews.pro/news/building-ratatop-a-rust-system-monitor-with-ratatui", "canonical_source": "https://dev.to/lovestaco/building-ratatop-a-rust-system-monitor-with-ratatui-50n", "published_at": "2026-07-28 12:24:46+00:00", "updated_at": "2026-07-28 12:34:12.354581+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Maneshwar", "ratatop", "ratatui", "Bubble Tea", "peektea", "Orhun Parmaksız", "tui-rs"], "alternates": {"html": "https://wpnews.pro/news/building-ratatop-a-rust-system-monitor-with-ratatui", "markdown": "https://wpnews.pro/news/building-ratatop-a-rust-system-monitor-with-ratatui.md", "text": "https://wpnews.pro/news/building-ratatop-a-rust-system-monitor-with-ratatui.txt", "jsonld": "https://wpnews.pro/news/building-ratatop-a-rust-system-monitor-with-ratatui.jsonld"}}