{"id":51920,"date":"2025-11-16T08:06:56","date_gmt":"2025-11-16T08:06:56","guid":{"rendered":"https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/"},"modified":"2025-11-16T08:06:56","modified_gmt":"2025-11-16T08:06:56","slug":"how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual","status":"publish","type":"post","link":"https:\/\/youzum.net\/ja\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/","title":{"rendered":"How to Design a Fully Interactive, Reactive, and Dynamic Terminal-Based Data Dashboard Using Textual?"},"content":{"rendered":"<p>In this tutorial, we build an advanced interactive dashboard using <a href=\"https:\/\/github.com\/Textualize\/textual\"><strong>Textual<\/strong><\/a>, and we explore how terminal-first UI frameworks can feel as expressive and dynamic as modern web dashboards. As we write and run each snippet, we actively construct the interface piece by piece, widgets, layouts, reactive state, and event flows, so we can see how Textual behaves like a live UI engine right inside Google Colab. By the end, we notice how naturally we can blend tables, trees, forms, and progress indicators into a cohesive application that feels fast, clean, and responsive. Check out the\u00a0<strong><a href=\"https:\/\/github.com\/Marktechpost\/AI-Tutorial-Codes-Included\/blob\/main\/Data%20Science\/advanced_textual_data_dashboard_Marktechpost.ipynb\" target=\"_blank\" rel=\"noreferrer noopener\">FULL CODES here<\/a><\/strong>.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">!pip install textual textual-web nest-asyncio\n\n\nfrom textual.app import App, ComposeResult\nfrom textual.containers import Container, Horizontal, Vertical\nfrom textual.widgets import (\n   Header, Footer, Button, DataTable, Static, Input,\n   Label, ProgressBar, Tree, Select\n)\nfrom textual.reactive import reactive\nfrom textual import on\nfrom datetime import datetime\nimport random\n\n\nclass StatsCard(Static):\n   value = reactive(0)\n  \n   def __init__(self, title: str, *args, **kwargs):\n       super().__init__(*args, **kwargs)\n       self.title = title\n      \n   def compose(self) -&gt; ComposeResult:\n       yield Label(self.title)\n       yield Label(str(self.value), id=\"stat-value\")\n  \n   def watch_value(self, new_value: int) -&gt; None:\n       if self.is_mounted:\n           try:\n               self.query_one(\"#stat-value\", Label).update(str(new_value))\n           except Exception:\n               pass<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We set up the environment and import all the necessary components to build our Textual application. As we define the StatsCard widget, we establish a reusable component that reacts to changes in value and updates itself automatically. We begin to see how Textual\u2019s reactive system lets us create dynamic UI elements with minimal effort. Check out the\u00a0<strong><a href=\"https:\/\/github.com\/Marktechpost\/AI-Tutorial-Codes-Included\/blob\/main\/Data%20Science\/advanced_textual_data_dashboard_Marktechpost.ipynb\" target=\"_blank\" rel=\"noreferrer noopener\">FULL CODES here<\/a><\/strong>.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">class DataDashboard(App):\n   CSS = \"\"\"\n   Screen { background: $surface; }\n   #main-container { height: 100%; padding: 1; }\n   #stats-row { height: auto; margin-bottom: 1; }\n   StatsCard { border: solid $primary; height: 5; padding: 1; margin-right: 1; width: 1fr; }\n   #stat-value { text-style: bold; color: $accent; content-align: center middle; }\n   #control-panel { height: 12; border: solid $secondary; padding: 1; margin-bottom: 1; }\n   #data-section { height: 1fr; }\n   #left-panel { width: 30; border: solid $secondary; padding: 1; margin-right: 1; }\n   DataTable { height: 100%; border: solid $primary; }\n   Input { margin: 1 0; }\n   Button { margin: 1 1 1 0; }\n   ProgressBar { margin: 1 0; }\n   \"\"\"\n  \n   BINDINGS = [\n       (\"d\", \"toggle_dark\", \"Toggle Dark Mode\"),\n       (\"q\", \"quit\", \"Quit\"),\n       (\"a\", \"add_row\", \"Add Row\"),\n       (\"c\", \"clear_table\", \"Clear Table\"),\n   ]\n  \n   total_rows = reactive(0)\n   total_sales = reactive(0)\n   avg_rating = reactive(0.0)<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We define the DataDashboard class and configure global styles, key bindings, and reactive attributes. We decide how the app should look and behave right from the top, giving us full control over themes and interactivity. This structure helps us create a polished dashboard without writing any HTML or JS. Check out the\u00a0<strong><a href=\"https:\/\/github.com\/Marktechpost\/AI-Tutorial-Codes-Included\/blob\/main\/Data%20Science\/advanced_textual_data_dashboard_Marktechpost.ipynb\" target=\"_blank\" rel=\"noreferrer noopener\">FULL CODES here<\/a><\/strong>.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\">  def compose(self) -&gt; ComposeResult:\n       yield Header(show_clock=True)\n      \n       with Container(id=\"main-container\"):\n           with Horizontal(id=\"stats-row\"):\n               yield StatsCard(\"Total Rows\", id=\"card-rows\")\n               yield StatsCard(\"Total Sales\", id=\"card-sales\")\n               yield StatsCard(\"Avg Rating\", id=\"card-rating\")\n          \n           with Vertical(id=\"control-panel\"):\n               yield Input(placeholder=\"Product Name\", id=\"input-name\")\n               yield Select(\n                   [(\"Electronics\", \"electronics\"),\n                    (\"Books\", \"books\"),\n                    (\"Clothing\", \"clothing\")],\n                   prompt=\"Select Category\",\n                   id=\"select-category\"\n               )\n               with Horizontal():\n                   yield Button(\"Add Row\", variant=\"primary\", id=\"btn-add\")\n                   yield Button(\"Clear Table\", variant=\"warning\", id=\"btn-clear\")\n                   yield Button(\"Generate Data\", variant=\"success\", id=\"btn-generate\")\n               yield ProgressBar(total=100, id=\"progress\")\n          \n           with Horizontal(id=\"data-section\"):\n               with Container(id=\"left-panel\"):\n                   yield Label(\"Navigation\")\n                   tree = Tree(\"Dashboard\")\n                   tree.root.expand()\n                   products = tree.root.add(\"Products\", expand=True)\n                   products.add_leaf(\"Electronics\")\n                   products.add_leaf(\"Books\")\n                   products.add_leaf(\"Clothing\")\n                   tree.root.add_leaf(\"Reports\")\n                   tree.root.add_leaf(\"Settings\")\n                   yield tree\n              \n               yield DataTable(id=\"data-table\")\n      \n       yield Footer()<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We compose the entire UI layout, arranging containers, cards, form inputs, buttons, a navigation tree, and a data table. As we structure these components, we watch the interface take shape exactly the way we envision it. This snippet lets us design the visual skeleton of the dashboard in a clean, declarative manner. Check out the\u00a0<strong><a href=\"https:\/\/github.com\/Marktechpost\/AI-Tutorial-Codes-Included\/blob\/main\/Data%20Science\/advanced_textual_data_dashboard_Marktechpost.ipynb\" target=\"_blank\" rel=\"noreferrer noopener\">FULL CODES here<\/a><\/strong>.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\"> def on_mount(self) -&gt; None:\n       table = self.query_one(DataTable)\n       table.add_columns(\"ID\", \"Product\", \"Category\", \"Price\", \"Sales\", \"Rating\")\n       table.cursor_type = \"row\"\n       self.generate_sample_data(5)\n       self.set_interval(0.1, self.update_progress)\n  \n   def generate_sample_data(self, count: int = 5) -&gt; None:\n       table = self.query_one(DataTable)\n       categories = [\"Electronics\", \"Books\", \"Clothing\"]\n       products = {\n           \"Electronics\": [\"Laptop\", \"Phone\", \"Tablet\", \"Headphones\"],\n           \"Books\": [\"Novel\", \"Textbook\", \"Magazine\", \"Comic\"],\n           \"Clothing\": [\"Shirt\", \"Pants\", \"Jacket\", \"Shoes\"]\n       }\n      \n       for _ in range(count):\n           category = random.choice(categories)\n           product = random.choice(products[category])\n           row_id = self.total_rows + 1\n           price = round(random.uniform(10, 500), 2)\n           sales = random.randint(1, 100)\n           rating = round(random.uniform(1, 5), 1)\n          \n           table.add_row(\n               str(row_id),\n               product,\n               category,\n               f\"${price}\",\n               str(sales),\n               str(rating)\n           )\n          \n           self.total_rows += 1\n           self.total_sales += sales\n      \n       self.update_stats()\n  \n   def update_stats(self) -&gt; None:\n       self.query_one(\"#card-rows\", StatsCard).value = self.total_rows\n       self.query_one(\"#card-sales\", StatsCard).value = self.total_sales\n      \n       if self.total_rows &gt; 0:\n           table = self.query_one(DataTable)\n           total_rating = sum(float(row[5]) for row in table.rows)\n           self.avg_rating = round(total_rating \/ self.total_rows, 2)\n           self.query_one(\"#card-rating\", StatsCard).value = self.avg_rating\n  \n   def update_progress(self) -&gt; None:\n       progress = self.query_one(ProgressBar)\n       progress.advance(1)\n       if progress.progress &gt;= 100:\n           progress.progress = 0<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We implement all the logic for generating data, computing statistics, animating progress, and updating cards. We see how quickly we can bind backend logic to frontend components using Textual\u2019s reactive model. This step makes the dashboard feel alive as numbers update instantly and progress bars animate smoothly. Check out the\u00a0<strong><a href=\"https:\/\/github.com\/Marktechpost\/AI-Tutorial-Codes-Included\/blob\/main\/Data%20Science\/advanced_textual_data_dashboard_Marktechpost.ipynb\" target=\"_blank\" rel=\"noreferrer noopener\">FULL CODES here<\/a><\/strong>.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\"no-line-numbers\"><code class=\"no-wrap language-php\"> @on(Button.Pressed, \"#btn-add\")\n   def handle_add_button(self) -&gt; None:\n       name_input = self.query_one(\"#input-name\", Input)\n       category = self.query_one(\"#select-category\", Select).value\n      \n       if name_input.value and category:\n           table = self.query_one(DataTable)\n           row_id = self.total_rows + 1\n           price = round(random.uniform(10, 500), 2)\n           sales = random.randint(1, 100)\n           rating = round(random.uniform(1, 5), 1)\n          \n           table.add_row(\n               str(row_id),\n               name_input.value,\n               str(category),\n               f\"${price}\",\n               str(sales),\n               str(rating)\n           )\n          \n           self.total_rows += 1\n           self.total_sales += sales\n           self.update_stats()\n           name_input.value = \"\"\n  \n   @on(Button.Pressed, \"#btn-clear\")\n   def handle_clear_button(self) -&gt; None:\n       table = self.query_one(DataTable)\n       table.clear()\n       self.total_rows = 0\n       self.total_sales = 0\n       self.avg_rating = 0\n       self.update_stats()\n  \n   @on(Button.Pressed, \"#btn-generate\")\n   def handle_generate_button(self) -&gt; None:\n       self.generate_sample_data(10)\n  \n   def action_toggle_dark(self) -&gt; None:\n       self.dark = not self.dark\n  \n   def action_add_row(self) -&gt; None:\n       self.handle_add_button()\n  \n   def action_clear_table(self) -&gt; None:\n       self.handle_clear_button()\n\n\n\n\nif __name__ == \"__main__\":\n   import nest_asyncio\n   nest_asyncio.apply()\n   app = DataDashboard()\n   app.run()<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We connect UI events to backend actions using button handlers, keyboard shortcuts, and app-level functions. As we run the app, we interact with a fully functional dashboard that responds instantly to every click and command. This snippet completes the application and demonstrates how easily Textual enables us to build dynamic, state-driven UIs.<\/p>\n<p>In conclusion, we see the whole dashboard come together in a fully functional, interactive form that runs directly from a notebook environment. We experience firsthand how Textual lets us design terminal UIs with the structure and feel of web apps, while staying entirely in Python. This tutorial leaves us confident that we can extend this foundation, even adding charts, API feeds, and multi-page navigation, as we continue to experiment with Textual\u2019s modern reactive UI capabilities.<\/p>\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n<p>Check out the\u00a0<strong><a href=\"https:\/\/github.com\/Marktechpost\/AI-Tutorial-Codes-Included\/blob\/main\/Data%20Science\/advanced_textual_data_dashboard_Marktechpost.ipynb\" target=\"_blank\" rel=\"noreferrer noopener\">FULL CODES here<\/a><\/strong>.\u00a0Feel free to check out our\u00a0<strong><mark><a href=\"https:\/\/github.com\/Marktechpost\/AI-Tutorial-Codes-Included\" target=\"_blank\" rel=\"noreferrer noopener\">GitHub Page for Tutorials, Codes and Notebooks<\/a><\/mark><\/strong>.\u00a0Also,\u00a0feel free to follow us on\u00a0<strong><a href=\"https:\/\/x.com\/intent\/follow?screen_name=marktechpost\" target=\"_blank\" rel=\"noreferrer noopener\"><mark>Twitter<\/mark><\/a><\/strong>\u00a0and don\u2019t forget to join our\u00a0<strong><a href=\"https:\/\/www.reddit.com\/r\/machinelearningnews\/\" target=\"_blank\" rel=\"noreferrer noopener\">100k+ ML SubReddit<\/a><\/strong>\u00a0and Subscribe to\u00a0<strong><a href=\"https:\/\/www.aidevsignals.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">our Newsletter<\/a><\/strong>. Wait! are you on telegram?\u00a0<strong><a href=\"https:\/\/t.me\/machinelearningresearchnews\" target=\"_blank\" rel=\"noreferrer noopener\">now you can join us on telegram as well.<\/a><\/strong><\/p>\n<p>The post <a href=\"https:\/\/www.marktechpost.com\/2025\/11\/15\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/\">How to Design a Fully Interactive, Reactive, and Dynamic Terminal-Based Data Dashboard Using Textual?<\/a> appeared first on <a href=\"https:\/\/www.marktechpost.com\/\">MarkTechPost<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>In this tutorial, we build an advanced interactive dashboard using Textual, and we explore how terminal-first UI frameworks can feel as expressive and dynamic as modern web dashboards. As we write and run each snippet, we actively construct the interface piece by piece, widgets, layouts, reactive state, and event flows, so we can see how Textual behaves like a live UI engine right inside Google Colab. By the end, we notice how naturally we can blend tables, trees, forms, and progress indicators into a cohesive application that feels fast, clean, and responsive. Check out the\u00a0FULL CODES here. Copy CodeCopiedUse a different Browser !pip install textual textual-web nest-asyncio from textual.app import App, ComposeResult from textual.containers import Container, Horizontal, Vertical from textual.widgets import ( Header, Footer, Button, DataTable, Static, Input, Label, ProgressBar, Tree, Select ) from textual.reactive import reactive from textual import on from datetime import datetime import random class StatsCard(Static): value = reactive(0) def __init__(self, title: str, *args, **kwargs): super().__init__(*args, **kwargs) self.title = title def compose(self) -&gt; ComposeResult: yield Label(self.title) yield Label(str(self.value), id=&#8221;stat-value&#8221;) def watch_value(self, new_value: int) -&gt; None: if self.is_mounted: try: self.query_one(&#8220;#stat-value&#8221;, Label).update(str(new_value)) except Exception: pass We set up the environment and import all the necessary components to build our Textual application. As we define the StatsCard widget, we establish a reusable component that reacts to changes in value and updates itself automatically. We begin to see how Textual\u2019s reactive system lets us create dynamic UI elements with minimal effort. Check out the\u00a0FULL CODES here. Copy CodeCopiedUse a different Browser class DataDashboard(App): CSS = &#8220;&#8221;&#8221; 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; } &#8220;&#8221;&#8221; BINDINGS = [ (&#8220;d&#8221;, &#8220;toggle_dark&#8221;, &#8220;Toggle Dark Mode&#8221;), (&#8220;q&#8221;, &#8220;quit&#8221;, &#8220;Quit&#8221;), (&#8220;a&#8221;, &#8220;add_row&#8221;, &#8220;Add Row&#8221;), (&#8220;c&#8221;, &#8220;clear_table&#8221;, &#8220;Clear Table&#8221;), ] total_rows = reactive(0) total_sales = reactive(0) avg_rating = reactive(0.0) We define the DataDashboard class and configure global styles, key bindings, and reactive attributes. We decide how the app should look and behave right from the top, giving us full control over themes and interactivity. This structure helps us create a polished dashboard without writing any HTML or JS. Check out the\u00a0FULL CODES here. Copy CodeCopiedUse a different Browser def compose(self) -&gt; ComposeResult: yield Header(show_clock=True) with Container(id=&#8221;main-container&#8221;): with Horizontal(id=&#8221;stats-row&#8221;): yield StatsCard(&#8220;Total Rows&#8221;, id=&#8221;card-rows&#8221;) yield StatsCard(&#8220;Total Sales&#8221;, id=&#8221;card-sales&#8221;) yield StatsCard(&#8220;Avg Rating&#8221;, id=&#8221;card-rating&#8221;) with Vertical(id=&#8221;control-panel&#8221;): yield Input(placeholder=&#8221;Product Name&#8221;, id=&#8221;input-name&#8221;) yield Select( [(&#8220;Electronics&#8221;, &#8220;electronics&#8221;), (&#8220;Books&#8221;, &#8220;books&#8221;), (&#8220;Clothing&#8221;, &#8220;clothing&#8221;)], prompt=&#8221;Select Category&#8221;, id=&#8221;select-category&#8221; ) with Horizontal(): yield Button(&#8220;Add Row&#8221;, variant=&#8221;primary&#8221;, id=&#8221;btn-add&#8221;) yield Button(&#8220;Clear Table&#8221;, variant=&#8221;warning&#8221;, id=&#8221;btn-clear&#8221;) yield Button(&#8220;Generate Data&#8221;, variant=&#8221;success&#8221;, id=&#8221;btn-generate&#8221;) yield ProgressBar(total=100, id=&#8221;progress&#8221;) with Horizontal(id=&#8221;data-section&#8221;): with Container(id=&#8221;left-panel&#8221;): yield Label(&#8220;Navigation&#8221;) tree = Tree(&#8220;Dashboard&#8221;) tree.root.expand() products = tree.root.add(&#8220;Products&#8221;, expand=True) products.add_leaf(&#8220;Electronics&#8221;) products.add_leaf(&#8220;Books&#8221;) products.add_leaf(&#8220;Clothing&#8221;) tree.root.add_leaf(&#8220;Reports&#8221;) tree.root.add_leaf(&#8220;Settings&#8221;) yield tree yield DataTable(id=&#8221;data-table&#8221;) yield Footer() We compose the entire UI layout, arranging containers, cards, form inputs, buttons, a navigation tree, and a data table. As we structure these components, we watch the interface take shape exactly the way we envision it. This snippet lets us design the visual skeleton of the dashboard in a clean, declarative manner. Check out the\u00a0FULL CODES here. Copy CodeCopiedUse a different Browser def on_mount(self) -&gt; None: table = self.query_one(DataTable) table.add_columns(&#8220;ID&#8221;, &#8220;Product&#8221;, &#8220;Category&#8221;, &#8220;Price&#8221;, &#8220;Sales&#8221;, &#8220;Rating&#8221;) table.cursor_type = &#8220;row&#8221; self.generate_sample_data(5) self.set_interval(0.1, self.update_progress) def generate_sample_data(self, count: int = 5) -&gt; None: table = self.query_one(DataTable) categories = [&#8220;Electronics&#8221;, &#8220;Books&#8221;, &#8220;Clothing&#8221;] products = { &#8220;Electronics&#8221;: [&#8220;Laptop&#8221;, &#8220;Phone&#8221;, &#8220;Tablet&#8221;, &#8220;Headphones&#8221;], &#8220;Books&#8221;: [&#8220;Novel&#8221;, &#8220;Textbook&#8221;, &#8220;Magazine&#8221;, &#8220;Comic&#8221;], &#8220;Clothing&#8221;: [&#8220;Shirt&#8221;, &#8220;Pants&#8221;, &#8220;Jacket&#8221;, &#8220;Shoes&#8221;] } for _ in range(count): category = random.choice(categories) product = random.choice(products[category]) 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, f&#8221;${price}&#8221;, str(sales), str(rating) ) self.total_rows += 1 self.total_sales += sales self.update_stats() def update_stats(self) -&gt; None: self.query_one(&#8220;#card-rows&#8221;, StatsCard).value = self.total_rows self.query_one(&#8220;#card-sales&#8221;, StatsCard).value = self.total_sales if self.total_rows &gt; 0: table = self.query_one(DataTable) total_rating = sum(float(row[5]) for row in table.rows) self.avg_rating = round(total_rating \/ self.total_rows, 2) self.query_one(&#8220;#card-rating&#8221;, StatsCard).value = self.avg_rating def update_progress(self) -&gt; None: progress = self.query_one(ProgressBar) progress.advance(1) if progress.progress &gt;= 100: progress.progress = 0 We implement all the logic for generating data, computing statistics, animating progress, and updating cards. We see how quickly we can bind backend logic to frontend components using Textual\u2019s reactive model. This step makes the dashboard feel alive as numbers update instantly and progress bars animate smoothly. Check out the\u00a0FULL CODES here. Copy CodeCopiedUse a different Browser @on(Button.Pressed, &#8220;#btn-add&#8221;) def handle_add_button(self) -&gt; None: name_input = self.query_one(&#8220;#input-name&#8221;, Input) category = self.query_one(&#8220;#select-category&#8221;, Select).value if name_input.value and 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), f&#8221;${price}&#8221;, str(sales), str(rating) ) self.total_rows += 1 self.total_sales += sales self.update_stats() name_input.value = &#8220;&#8221; @on(Button.Pressed, &#8220;#btn-clear&#8221;) def handle_clear_button(self) -&gt; 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, &#8220;#btn-generate&#8221;) def handle_generate_button(self) -&gt; None: self.generate_sample_data(10) def action_toggle_dark(self) -&gt; None: self.dark = not self.dark def action_add_row(self) -&gt; None: self.handle_add_button() def action_clear_table(self) -&gt; None: self.handle_clear_button() if __name__ == &#8220;__main__&#8221;: import nest_asyncio nest_asyncio.apply() app = DataDashboard() app.run() We connect UI events to backend actions using button handlers, keyboard shortcuts, and app-level functions. As we run the app, we interact with a fully functional dashboard that responds instantly to every click and command. This snippet completes the application and demonstrates how easily Textual enables us to build dynamic, state-driven UIs. In conclusion, we see the whole dashboard come together in a fully functional, interactive form that runs directly from a notebook environment. We experience firsthand how Textual lets us design terminal UIs with the structure and<\/p>","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"pmpro_default_level":"","site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"_pvb_checkbox_block_on_post":false,"footnotes":""},"categories":[52,5,7,1],"tags":[],"class_list":["post-51920","post","type-post","status-publish","format-standard","hentry","category-ai-club","category-committee","category-news","category-uncategorized","pmpro-has-access"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v25.3 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>How to Design a Fully Interactive, Reactive, and Dynamic Terminal-Based Data Dashboard Using Textual? - YouZum<\/title>\n<meta name=\"description\" content=\"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/youzum.net\/ja\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/\" \/>\n<meta property=\"og:locale\" content=\"ja_JP\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How to Design a Fully Interactive, Reactive, and Dynamic Terminal-Based Data Dashboard Using Textual? - YouZum\" \/>\n<meta property=\"og:description\" content=\"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19\" \/>\n<meta property=\"og:url\" content=\"https:\/\/youzum.net\/ja\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/\" \/>\n<meta property=\"og:site_name\" content=\"YouZum\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/DroneAssociationTH\/\" \/>\n<meta property=\"article:published_time\" content=\"2025-11-16T08:06:56+00:00\" \/>\n<meta name=\"author\" content=\"admin NU\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u57f7\u7b46\u8005\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin NU\" \/>\n\t<meta name=\"twitter:label2\" content=\"\u63a8\u5b9a\u8aad\u307f\u53d6\u308a\u6642\u9593\" \/>\n\t<meta name=\"twitter:data2\" content=\"6\u5206\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/\"},\"author\":{\"name\":\"admin NU\",\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/person\/97fa48242daf3908e4d9a5f26f4a059c\"},\"headline\":\"How to Design a Fully Interactive, Reactive, and Dynamic Terminal-Based Data Dashboard Using Textual?\",\"datePublished\":\"2025-11-16T08:06:56+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/\"},\"wordCount\":579,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/yousum.gpucore.co\/#organization\"},\"articleSection\":[\"AI\",\"Committee\",\"News\",\"Uncategorized\"],\"inLanguage\":\"ja\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/\",\"url\":\"https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/\",\"name\":\"How to Design a Fully Interactive, Reactive, and Dynamic Terminal-Based Data Dashboard Using Textual? - YouZum\",\"isPartOf\":{\"@id\":\"https:\/\/yousum.gpucore.co\/#website\"},\"datePublished\":\"2025-11-16T08:06:56+00:00\",\"description\":\"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19\",\"breadcrumb\":{\"@id\":\"https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/#breadcrumb\"},\"inLanguage\":\"ja\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/youzum.net\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How to Design a Fully Interactive, Reactive, and Dynamic Terminal-Based Data Dashboard Using Textual?\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/yousum.gpucore.co\/#website\",\"url\":\"https:\/\/yousum.gpucore.co\/\",\"name\":\"YouSum\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/yousum.gpucore.co\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/yousum.gpucore.co\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"ja\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/yousum.gpucore.co\/#organization\",\"name\":\"Drone Association Thailand\",\"url\":\"https:\/\/yousum.gpucore.co\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"ja\",\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/youzum.net\/wp-content\/uploads\/2024\/11\/tranparent-logo.png\",\"contentUrl\":\"https:\/\/youzum.net\/wp-content\/uploads\/2024\/11\/tranparent-logo.png\",\"width\":300,\"height\":300,\"caption\":\"Drone Association Thailand\"},\"image\":{\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/DroneAssociationTH\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/person\/97fa48242daf3908e4d9a5f26f4a059c\",\"name\":\"admin NU\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"ja\",\"@id\":\"https:\/\/yousum.gpucore.co\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/youzum.net\/wp-content\/uploads\/avatars\/2\/1746849356-bpfull.png\",\"contentUrl\":\"https:\/\/youzum.net\/wp-content\/uploads\/avatars\/2\/1746849356-bpfull.png\",\"caption\":\"admin NU\"},\"url\":\"https:\/\/youzum.net\/ja\/members\/adminnu\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"How to Design a Fully Interactive, Reactive, and Dynamic Terminal-Based Data Dashboard Using Textual? - YouZum","description":"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/youzum.net\/ja\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/","og_locale":"ja_JP","og_type":"article","og_title":"How to Design a Fully Interactive, Reactive, and Dynamic Terminal-Based Data Dashboard Using Textual? - YouZum","og_description":"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19","og_url":"https:\/\/youzum.net\/ja\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/","og_site_name":"YouZum","article_publisher":"https:\/\/www.facebook.com\/DroneAssociationTH\/","article_published_time":"2025-11-16T08:06:56+00:00","author":"admin NU","twitter_card":"summary_large_image","twitter_misc":{"\u57f7\u7b46\u8005":"admin NU","\u63a8\u5b9a\u8aad\u307f\u53d6\u308a\u6642\u9593":"6\u5206"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/#article","isPartOf":{"@id":"https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/"},"author":{"name":"admin NU","@id":"https:\/\/yousum.gpucore.co\/#\/schema\/person\/97fa48242daf3908e4d9a5f26f4a059c"},"headline":"How to Design a Fully Interactive, Reactive, and Dynamic Terminal-Based Data Dashboard Using Textual?","datePublished":"2025-11-16T08:06:56+00:00","mainEntityOfPage":{"@id":"https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/"},"wordCount":579,"commentCount":0,"publisher":{"@id":"https:\/\/yousum.gpucore.co\/#organization"},"articleSection":["AI","Committee","News","Uncategorized"],"inLanguage":"ja","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/","url":"https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/","name":"How to Design a Fully Interactive, Reactive, and Dynamic Terminal-Based Data Dashboard Using Textual? - YouZum","isPartOf":{"@id":"https:\/\/yousum.gpucore.co\/#website"},"datePublished":"2025-11-16T08:06:56+00:00","description":"\u0e01\u0e34\u0e08\u0e01\u0e23\u0e23\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e42\u0e14\u0e23\u0e19","breadcrumb":{"@id":"https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/#breadcrumb"},"inLanguage":"ja","potentialAction":[{"@type":"ReadAction","target":["https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/youzum.net\/how-to-design-a-fully-interactive-reactive-and-dynamic-terminal-based-data-dashboard-using-textual\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/youzum.net\/"},{"@type":"ListItem","position":2,"name":"How to Design a Fully Interactive, Reactive, and Dynamic Terminal-Based Data Dashboard Using Textual?"}]},{"@type":"WebSite","@id":"https:\/\/yousum.gpucore.co\/#website","url":"https:\/\/yousum.gpucore.co\/","name":"YouSum","description":"","publisher":{"@id":"https:\/\/yousum.gpucore.co\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/yousum.gpucore.co\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"ja"},{"@type":"Organization","@id":"https:\/\/yousum.gpucore.co\/#organization","name":"Drone Association Thailand","url":"https:\/\/yousum.gpucore.co\/","logo":{"@type":"ImageObject","inLanguage":"ja","@id":"https:\/\/yousum.gpucore.co\/#\/schema\/logo\/image\/","url":"https:\/\/youzum.net\/wp-content\/uploads\/2024\/11\/tranparent-logo.png","contentUrl":"https:\/\/youzum.net\/wp-content\/uploads\/2024\/11\/tranparent-logo.png","width":300,"height":300,"caption":"Drone Association Thailand"},"image":{"@id":"https:\/\/yousum.gpucore.co\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/DroneAssociationTH\/"]},{"@type":"Person","@id":"https:\/\/yousum.gpucore.co\/#\/schema\/person\/97fa48242daf3908e4d9a5f26f4a059c","name":"admin NU","image":{"@type":"ImageObject","inLanguage":"ja","@id":"https:\/\/yousum.gpucore.co\/#\/schema\/person\/image\/","url":"https:\/\/youzum.net\/wp-content\/uploads\/avatars\/2\/1746849356-bpfull.png","contentUrl":"https:\/\/youzum.net\/wp-content\/uploads\/avatars\/2\/1746849356-bpfull.png","caption":"admin NU"},"url":"https:\/\/youzum.net\/ja\/members\/adminnu\/"}]}},"rttpg_featured_image_url":null,"rttpg_author":{"display_name":"admin NU","author_link":"https:\/\/youzum.net\/ja\/members\/adminnu\/"},"rttpg_comment":0,"rttpg_category":"<a href=\"https:\/\/youzum.net\/ja\/category\/ai-club\/\" rel=\"category tag\">AI<\/a> <a href=\"https:\/\/youzum.net\/ja\/category\/committee\/\" rel=\"category tag\">Committee<\/a> <a href=\"https:\/\/youzum.net\/ja\/category\/news\/\" rel=\"category tag\">News<\/a> <a href=\"https:\/\/youzum.net\/ja\/category\/uncategorized\/\" rel=\"category tag\">Uncategorized<\/a>","rttpg_excerpt":"In this tutorial, we build an advanced interactive dashboard using Textual, and we explore how terminal-first UI frameworks can feel as expressive and dynamic as modern web dashboards. As we write and run each snippet, we actively construct the interface piece by piece, widgets, layouts, reactive state, and event flows, so we can see how&hellip;","_links":{"self":[{"href":"https:\/\/youzum.net\/ja\/wp-json\/wp\/v2\/posts\/51920","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/youzum.net\/ja\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/youzum.net\/ja\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/youzum.net\/ja\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/youzum.net\/ja\/wp-json\/wp\/v2\/comments?post=51920"}],"version-history":[{"count":0,"href":"https:\/\/youzum.net\/ja\/wp-json\/wp\/v2\/posts\/51920\/revisions"}],"wp:attachment":[{"href":"https:\/\/youzum.net\/ja\/wp-json\/wp\/v2\/media?parent=51920"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/youzum.net\/ja\/wp-json\/wp\/v2\/categories?post=51920"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/youzum.net\/ja\/wp-json\/wp\/v2\/tags?post=51920"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}