Building Rich terminal dashboards
Rich has become a popular (20K stars on GH) way of beautifying CLIs, and I'm pleased to see a number of projects using it.
Since Rich is mature and battle-tested now, I had considered winding-down development. Until, I saw this tweet:
The Tweet
Do you want to see something really cool you can do with @willmcgugan 's rich library?
— Hamel Husain (@HamelHusain) February 5, 2021
Checkout ghtop https://t.co/mn7oLbpw8e. It's a really fun CLI tool that demonstrates the power of rich (and other things).
Examples below 🧵👇 (1/8)
@HamelHusain and @jeremyphoward used Rich to enhance ghtop (a repo owned by the CEO of Github, no less). Ghtop shows a realtime a stream of events from the Github platform. And it looks good! So good that I realised how much potential Rich has for these type of htop-like applications.
Hamel and Jeremy had to overcome a few technical hurdles to make that work. Fortunately this will no longer be required as the latest version of Rich has first-class support for full-screen interfaces via a new Layout system.
Full-screen terminal interface
Here's a video demonstration of a terminal interface built with Layout:
Layout API
The API to create a flexible layout is surprisingly simple. You construct a Layout()
object, then call split()
to create sub-layouts. These sub-layouts may then be further divided. Layouts have a small number of settings which define their size relative to the terminal window. It's a simple system that can create terminal interfaces that almost resemble modern web apps.
Finally, some code
Here's how you would create a basic layout with a header, a footer, and two side-panels.
from rich.console import Console
from rich.layout import Layout
console = Console()
layout = Layout()
# Divide the "screen" in to three parts
layout.split(
Layout(name="header", size=3),
Layout(ratio=1, name="main"),
Layout(size=10, name="footer"),
)
# Divide the "main" layout in to "side" and "body"
layout["main"].split(
Layout(name="side"),
Layout(name="body", ratio=2),
direction="horizontal"
)
# Divide the "side" layout in to two
layout["side"].split(Layout(), Layout())
console.print(layout)
Running the code above produces the following output:

Terminal split in to 5 sub-layouts. The boxes are placeholders, you can insert any content in their place.
Any renderable (text, table, progress bars etc) may be placed inside those sub-layouts.
We can now use the Live class to create an application that adapts itself to the terminal window:
from rich.live import Live
from time import sleep
with Live(layout, screen=True):
while True:
sleep(1)
In a real app, that do-nothing loop will be doing something useful like pulling data from the network to update contents.
See the layout docs for an in-depth tutorial.
What's next?
I think it's clear that Rich is acquiring more TUI (text user interface) features, and I've decided not to fight it. Rich's core purpose is still to beautify CLI output, but I think there is an opportunity here for a new way to create terminal apps. Ultimately it will be less like curses and more like HTML in a browser.
There are a number of things to do before Rich could replace a full TUI library (keyboard and mouse input for one) but the potential is there. Stay tuned for progress.
Follow @willmcgugan for more Rich related news.
Thanks for sharing this. I have heard about Rich but never had a chance to take a deeper look. This looks beyond amazing, reaching the level of black-magic. Plus, the syntax of layout resembles flex box in React Native for app design, very intuitive.
Oh this looks amazing!
I have been using Rich at work to improve my tooling, and now with layouts and live display my mind is dreaming big.
Fantastic work
1995: graphical user interfaces are the future 2021: reinvented graphical user interfaces, but in caveman console interfaces... are the future?
Yes
In v10, they changed the Splitter class, so the example above should reflect:
GitHub PR 1142
Hey will, they changed the split class, can you update the post, the comment above doesnt work either
If it helps anyone looking at this, they changed things a little and this code won't work. It seems to me like making it:
instead will give the same output, but I don't know exactly how it looked like before. It basically looks like instead of the direction of the split being an argument the two directions are different functions.