We will build an advanced dashboard with Dashboard. TextualIn this article, we will explore how terminal first UI frameworks feel just as expressive and as dynamic as modern dashboards. We actively build the interface as we run and write each snippet. This includes widgets, responsive state, event flows and layouts. At the end of this tutorial, you will see how easily we were able to combine tables, tree forms and progress indicator into one cohesive app that felt fast, responsive and clean. Click here to see the FULL CODES here.
!pip install textual textual-web nest-asyncio
Import App from Textual.app, ComposeResult
Textual.containers.import Container, Horizontal.
Import textual.widgets (
Header, Footer, Button, DataTable, Static, Input,
Label Tree ProgressBar
)
Textual import of reactive
From textual import
Datetime can be imported from another datetime
Random Import
Class StatsCard (Static),
Value = reactive(0)
def __init__(self, title: str, *args, **kwargs):
Supermarkets are a great way to buy goods and services.().__init__(*args, **kwargs)
Title = self-title
def compose(self) -> ComposeResult:
"self-title" Label
yield Label(str(self.value), id="stat-value")
def watch_value(self, new_value: int) -> None:
If self.is_mounted
try:
self.query_one("#stat-value", Label).update(str(new_value))
Except Exception
pass
Set up your environment, and then import the components you need to create our Textual app. We create a component that is reusable and automatically updates when the value of a widget changes. Textual’s reactive system allows us to quickly create dynamic UI with little effort. Look at the FULL CODES here.
class DataDashboard(App):
The CSS code is: """
Screen { background: $surface; }
#main-container { height: 100%; padding: 1; }
#stats-row { height: auto; margin-bottom: 1; }
StatsCard { border: solid $primary; height: 5; padding: 1; margin-right: 1; width: 1fr; }
#stat-value { text-style: bold; color: $accent; content-align: center middle; }
#control-panel { height: 12; border: solid $secondary; padding: 1; margin-bottom: 1; }
#data-section { height: 1fr; }
#left-panel { width: 30; border: solid $secondary; padding: 1; margin-right: 1; }
DataTable { height: 100%; border: solid $primary; }
Input { margin: 1 0; }
Button { margin: 1 1 1 0; }
ProgressBar { margin: 1 0; }
"""
BINDINGS [
("d", "toggle_dark", "Toggle Dark Mode"),
("q", "quit", "Quit"),
("a", "add_row", "Add Row"),
("c", "clear_table", "Clear Table"),
]
total_rows = reactive(0)
total_sales = reactive(0)
avg_rating = reactive(0.0)
DataDashboard can be configured by configuring global styles, keys bindings, reactive attributes, etc. The app’s appearance and behavior are decided from the beginning. We have full control of themes and interaction. We can create a dashboard using this structure without needing to know HTML or JS. Click here to see the FULL CODES here.
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
With Container(id="main-container"):
With Horizontal(id="stats-row"):
StatsCard"Total Rows", id="card-rows")
StatsCard"Total Sales", id="card-sales")
StatsCard"Avg Rating", id="card-rating")
With Vertical(id="control-panel"):
It is a placeholder for the input (placeholder="Product Name", id="input-name")
Choose ()
[("Electronics", "electronics"),
("Books", "books"),
("Clothing", "clothing")],
prompt="Select Category",
id="select-category"
)
Horizontal():
Give Button"Add Row", variant="primary", id="btn-add")
Give Button"Clear Table", variant="warning", id="btn-clear")
Give Button"Generate Data", variant="success", id="btn-generate")
yield ProgressBar(total=100, id="progress")
With Horizontal(id="data-section"):
Container(id="left-panel"):
?????????????????????????????????????????????????????????????????????????????????????????????????????"Navigation")
Tree = Tree"Dashboard")
tree.root.expand()
Product = root.add ("Products", expand=True)
products.add_leaf("Electronics")
products.add_leaf("Books")
products.add_leaf("Clothing")
tree.root.add_leaf("Reports")
tree.root.add_leaf("Settings")
Yield tree
Yield DataTable (id=)"data-table")
The Footer()
The entire UI is composed by arranging the containers, forms, buttons, navigation trees, data tables, form inputs and cards. We can watch as the interface takes shape in the exact way that we want it to. It allows you to create the dashboard visual structure in an elegant, declarative way. See the FULL CODES here.
def on_mount(self) -> None:
table = self.query_one(DataTable)
table.add_columns("ID", "Product", "Category", "Price", "Sales", "Rating")
table.cursor_type = "row"
self.generate_sample_data(5)
self.set_interval(0.1, self.update_progress)
def generate_sample_data(self, count: int = 5) -> None:
table = self.query_one(DataTable)
Categories =Products = ["Electronics", "Books", "Clothing"]
products = {
"Electronics": ["Laptop", "Phone", "Tablet", "Headphones"],
"Books": ["Novel", "Textbook", "Magazine", "Comic"],
"Clothing": ["Shirt", "Pants", "Jacket", "Shoes"]
}
The range of _ is:
category = random.choice(categories)
product = random.choice(productsArtificial Intelligence)
row_id = self.total_rows + 1
price = round(random.uniform(10, 500), 2)
sales = random.randint(1, 100)
rating = round(random.uniform(1, 5), 1)
table.add_row(
str(row_id),
product,
category,
The f"${price}",
str(sales),
str(rating)
)
self.total_rows += 1
self.total_sales += sales
self.update_stats()
def update_stats(self) -> None:
self.query_one("#card-rows", StatsCard).value = self.total_rows
self.query_one("#card-sales", StatsCard).value = self.total_sales
if self.total_rows > 0:
table = self.query_one(DataTable)
total_rating = sum(float(row[5]Rows in table)
self.avg_rating = round(total_rating / self.total_rows, 2)
self.query_one("#card-rating", StatsCard).value = self.avg_rating
def update_progress(self) -> None:
progress = self.query_one(ProgressBar)
progress.advance(1)
if progress.progress >= 100:
progress.progress = 0
We then implement the entire logic to generate data, compute statistics, animate progress and update cards. Textual’s Reactive Model allows us to quickly bind the backend logic with frontend components. It is now possible to make the dashboard look alive by updating numbers instantly, and letting progress bars move smoothly. Look at the FULL CODES here.
@on(Button.Pressed, "#btn-add")
def handle_add_button(self) -> None:
name_input = self.query_one("#input-name", Input)
"category = self.query_one""#select-category", Select).value
If name_input.value is equal to category
table = self.query_one(DataTable)
row_id = self.total_rows + 1
price = round(random.uniform(10, 500), 2)
sales = random.randint(1, 100)
rating = round(random.uniform(1, 5), 1)
table.add_row(
str(row_id),
name_input.value,
str(category),
The f"${price}",
str(sales),
str(rating)
)
self.total_rows += 1
self.total_sales += sales
self.update_stats()
name_input.value = ""
@on(Button.Pressed, "#btn-clear")
def handle_clear_button(self) -> None:
table = self.query_one(DataTable)
table.clear()
self.total_rows = 0
self.total_sales = 0
self.avg_rating = 0
self.update_stats()
@on(Button.Pressed, "#btn-generate")
def handle_generate_button(self) -> None:
self.generate_sample_data(10)
def action_toggle_dark(self) -> None:
Not self.
def action_add_row(self) -> None:
self.handle_add_button()
def action_clear_table(self) -> None:
self.handle_clear_button()
If the __name__ equals "__main__":
import nest_asyncio
nest_asyncio.apply()
app = DataDashboard()
app.run()
We link UI events with backend actions by using keyboard shortcuts and functions at the app level. While running the application, we are interacting with a dashboard that is fully functional and responds to clicks and commands instantly. The snippet shows how Textual allows us to create dynamic and state-driven interfaces.
We see that the dashboard is now fully interactive and functional. It runs from within a notebook. Textual allows us to create terminal UIs that have the look and feel of web applications, all while remaining in Python. The tutorial gives us the confidence to continue experimenting with Textual’s reactive UI, adding more charts, feeds from APIs and multi-pages navigation.
Take a look at the FULL CODES here. Check out our GitHub Page for Tutorials, Codes and Notebooks. Also, feel free to follow us on Twitter Join our Facebook group! 100k+ ML SubReddit Subscribe now our Newsletter. Wait! Are you using Telegram? now you can join us on telegram as well.
Asif Razzaq, CEO of Marktechpost Media Inc. is a visionary engineer and entrepreneur who is dedicated to harnessing Artificial Intelligence’s potential for the social good. Marktechpost was his most recent venture. This platform, which specializes in covering machine learning and deep-learning news, is both technically solid and understandable to a broad audience. Over 2 million views per month are a testament to the platform’s popularity.

