SSH Screens Scrolling Wildly and the 2 AM Nightmare
2 AM. Server logs are pouring down like a waterfall on the pitch-black SSH screen. I’m straining my eyes to find an error line among tens of thousands of records from a hanging crawler bot. At this point, typing grep or tail -f repeatedly is pure torture. I suddenly thought: “Why not turn this black screen into a professional dashboard to monitor CPU, RAM, and real-time progress?”
In the past, building Terminal User Interfaces (TUI) with the curses library was a nightmare because you had to manually manage every pixel coordinate. But since discovering Textual, everything has changed. This library allows you to use CSS for layout and widgets like Table and Input just like web development. Everything runs smoothly right in your terminal.
Quick Start: 5 Minutes to Your First TUI Interface
Don’t waste time on dry theory. Let’s install and run a “Hello World” app that’s on a whole different level.
pip install textual
Create an app.py file and paste the code below. I’ve simplified the structure so you can easily visualize it:
from textual.app import App, ComposeResult
from textual.widgets import Header, Footer, Label, Button
class MyFirstTUI(App):
# Quick shortcuts
BINDINGS = [("d", "toggle_dark", "Light/Dark"), ("q", "quit", "Quit")]
def compose(self) -> ComposeResult:
"""Define UI components"""
yield Header(show_clock=True)
yield Label("Monitoring System - Status: Running...")
yield Button("Check Server", variant="success")
yield Footer()
def action_toggle_dark(self) -> None:
self.dark = not self.dark
if __name__ == "__main__":
app = MyFirstTUI()
app.run()
Run python app.py and enjoy the result. You have a full interface with a Header, Footer, and clickable buttons. Much cooler than those dry print() lines, right?
Why Textual is a Game Changer
When processing batch jobs with over 100,000 records for a client, I realized tqdm wasn’t enough. I needed to see average speed, error counts, and the logs of the last 5 records simultaneously. Textual solves this with an incredibly smart Reactive model.
The most valuable feature is the ability to separate logic and UI via TCSS (Textual CSS). You can adjust colors, margins, or padding without touching the Python logic. For example, to make a Label look more professional, use a style.tcss file:
Label {
width: 100%;
height: 3;
content-align: center middle;
background: $accent;
color: $text;
text-style: bold;
border: solid $secondary;
}
Just add CSS_PATH = "style.tcss" to your App class. Textual will automatically calculate the layout, so you no longer have to count every character to align the screen.
Building a Real-World Monitoring Dashboard
Instead of typing ps aux repeatedly, let’s build an auto-updating data table. This is how I manage background processes.
from textual.app import App, ComposeResult
from textual.widgets import DataTable, Header, Footer
from textual.containers import Container
import random
import asyncio
class MonitorApp(App):
CSS = "DataTable { height: 1fr; border: double $primary; }"
def compose(self) -> ComposeResult:
yield Header()
yield Container(DataTable())
yield Footer()
def on_mount(self) -> None:
table = self.query_one(DataTable)
table.add_columns("Service", "Status", "Uptime")
table.add_row("Nginx", "[green]Running[/green]", "12 days")
table.add_row("PostgreSQL", "[red]Stopped[/red]", "0 mins")
self.set_interval(2, self.update_data)
def update_data(self) -> None:
table = self.query_one(DataTable)
uptime = f"{random.randint(1, 60)} mins"
table.update_cell(row_index=0, column_index=2, value=uptime)
In this example, I use set_interval to update the UI periodically. This is a key technique when writing real-time monitoring tools without freezing the application.
Common Pitfalls to Avoid in Terminal UI
Building a TUI is very different from building a Web app. Here are 3 points I learned the hard way after hours of debugging:
- Terminal Emulator: Not every terminal supports 24-bit color. If you run it on a 10-year-old server, the interface will fall apart. Prioritize using
iTerm2,Windows Terminal, orKitty. - Event Loop: Textual runs on
asyncio. Atime.sleep(10)command will freeze the entire interface immediately. Always use async libraries likehttpxinstead ofrequests. - Resolution: Don’t use a fixed number of characters for the layout. Use
fr(fraction) units so the interface scales automatically when the user resizes the terminal window.
Small Tips to Make Your Tool Look ‘Pro’
To help your tool escape the ‘student project’ label, apply these 3 tricks:
- Rich API: Leverage tags like
[bold red]or[blink]to emphasize important warnings. - Input Validation: Use the
validatorsattribute in theInputwidget. It helps block errors as soon as a user enters an incorrect IP or Port format. - RichLog: Instead of
print(), useRichLog. It allows you to scroll through history without breaking other widgets on the screen.
Since switching my management scripts to TUI, handling late-night incidents has become much less stressful. Instead of typing commands in despair, I just look at the charts and click the restart button. If you have boring Python scripts, try giving them a new look with Textual today.

