How to Design Python-First Interactive Dashboards with Prefab Reactive UI Components and Static HTML Export Prefab released a tutorial demonstrating how to build interactive Python-first dashboards using its reactive UI components and static HTML export. The tutorial shows developers how to create a polished operations dashboard with charts, tables, filters, and forms entirely in Python without writing frontend code. The approach enables rapid prototyping and deployment of data-driven interfaces directly from Python environments like Google Colab. In this tutorial https://github.com/MARKTECHPOST-AI-MEDIA-INC/AI-Agents-Projects-Tutorials/blob/main/Data%20Analysis/prefab reactive dashboard tutorial Marktechpost.ipynb , we build a Prefab https://github.com/PrefectHQ/prefab application that demonstrates how to create interactive dashboards entirely in Python. We use Prefab’s component-based Python interface to design a polished operations dashboard with reactive state, charts, tables, filters, forms, tabs, alerts, metrics, and client-side actions. We generate realistic pipeline monitoring data, connect it to live UI controls, and export the final app as a static HTML dashboard that we can preview directly inside Google Colab. Through this workflow, we learn how Prefab lets us move from Python data logic to a modern React-powered user interface without having to write frontend code manually. Installing Prefab in Colab python import os import sys import base64 import subprocess from pathlib import Path from IPython.display import HTML, display, FileLink PREFAB VERSION = "0.20.2" APP PATH = Path "/content/prefab advanced tutorial app.py" HTML PATH = Path "/content/prefab advanced dashboard.html" subprocess.check call sys.executable, "-m", "pip", "install", "-q", f"prefab-ui=={PREFAB VERSION}", APP CODE = "" We set up the Colab environment by importing the required Python utilities and defining the Prefab version, app path, and HTML export path. We install the pinned prefab-ui package so that the tutorial runs consistently without version-related issues. We also initialize an empty APP CODE string, which we use to build the complete Prefab application step by step. APP CODE += r''' Generating Synthetic Operations Data python import random from collections import Counter, defaultdict from datetime import date, timedelta from prefab ui.actions import AppendState, OpenLink, PopState, SetState, ShowToast, ToggleState from prefab ui.app import PrefabApp from prefab ui.components import Alert, AlertDescription, AlertTitle, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Code, Column, DataTable, DataTableColumn, Form, Grid, H2, Input, Markdown, Mermaid, Metric, Muted, Progress, Ring, Row, Slider, Small, Switch, Tab, Tabs, Text from prefab ui.components.charts import BarChart, ChartSeries, LineChart, PieChart, RadarChart, ScatterChart, Sparkline from prefab ui.components.control flow import Else, ForEach, If from prefab ui.rx import EVENT, STATE random.seed 42 TODAY = date.today DATES = TODAY - timedelta days=29 - i for i in range 30 REGIONS = "All", "APAC", "EMEA", "NA", "LATAM" PIPELINES = "Customer 360 ETL", "Invoice OCR", "LLM Triage", "Risk Scoring", "Forecast Sync", "Warehouse Load", OWNERS = "Data Platform", "AI Apps", "Revenue Ops", "Risk Engineering" STATES = "Completed", "Completed", "Completed", "Completed", "Late", "Failed" PRIORITIES = "P0", "P1", "P2", "P3" runs = daily region rows = for d in DATES: for region in REGIONS 1: : region bias = { "APAC": 0.96, "EMEA": 0.94, "NA": 0.97, "LATAM": 0.91, } region volume = random.randint 32, 78 failures = 0 late = 0 total cost = 0.0 total latency = 0.0 total revenue = 0.0 for i in range volume : pipeline = random.choice PIPELINES owner = random.choice OWNERS state = random.choices STATES, weights= region bias 10, 6, 4, 3, 1.2, max 0.2, 1 - region bias 16 , , k=1, 0 duration = max 12, int random.gauss 95, 35 + 20 if state == "Late" else 0 + 45 if state == "Failed" else 0 , cost = round max 0.09, random.lognormvariate -1.15, 0.55 + duration / 1800 , 2 revenue = round random.uniform 1.2, 8.5 1.3 if state == "Completed" else 0.6 , 2 priority = random.choices PRIORITIES, weights= 1, 3, 7, 10 , k=1 0 if state == "Failed": failures += 1 if state == "Late": late += 1 total cost += cost total latency += duration total revenue += revenue if d = TODAY - timedelta days=10 and state in {"Failed", "Late"} or random.random < 0.05 : runs.append { "run id": f"{d.strftime '%m%d' }-{region :2 }-{len runs +1:04d}", "date": d.strftime "%Y-%m-%d" , "pipeline": pipeline, "owner": owner, "region": region, "state": state, "priority": priority, "duration s": duration, "cost usd": cost, "revenue k": revenue, "sla gap": round max 0, duration - 120 / 60, 1 , } daily region rows.append { "date": d.strftime "%b %d" , "region": region, "runs": volume, "failures": failures, "late": late, "success rate": round 100 volume - failures - late 0.35 / volume, 1 , "avg latency": round total latency / volume, 1 , "cost usd": round total cost, 2 , "revenue k": round total revenue, 1 , } runs = sorted runs, key=lambda r: r "priority" , r "state" = "Failed", -r "duration s" :80 def aggregate daily rows : by date = defaultdict lambda: { "date": "", "runs": 0, "failures": 0, "late": 0, "cost usd": 0.0, "revenue k": 0.0, "latency weighted": 0.0, } for r in rows: bucket = by date r "date" bucket "date" = r "date" bucket "runs" += r "runs" bucket "failures" += r "failures" bucket "late" += r "late" bucket "cost usd" += r "cost usd" bucket "revenue k" += r "revenue k" bucket "latency weighted" += r "avg latency" r "runs" out = for d in x.strftime "%b %d" for x in DATES : b = by date d if b "runs" : b "success rate" = round 100 b "runs" - b "failures" - b "late" 0.35 / b "runs" , 1 b "avg latency" = round b "latency weighted" / b "runs" , 1 b "cost usd" = round b "cost usd" , 2 b "revenue k" = round b "revenue k" , 1 del b "latency weighted" out.append dict b return out def aggregate regions rows : by region = defaultdict lambda: { "region": "", "runs": 0, "failures": 0, "late": 0, "cost usd": 0.0, "revenue k": 0.0, "latency weighted": 0.0, } for r in rows: b = by region r "region" b "region" = r "region" b "runs" += r "runs" b "failures" += r "failures" b "late" += r "late" b "cost usd" += r "cost usd" b "revenue k" += r "revenue k" b "latency weighted" += r "avg latency" r "runs" out = for region in REGIONS 1: : b = by region region b "success rate" = round 100 b "runs" - b "failures" - b "late" 0.35 / b "runs" , 1 b "avg latency" = round b "latency weighted" / b "runs" , 1 b "cost usd" = round b "cost usd" , 2 b "revenue k" = round b "revenue k" , 1 b "roi" = round b "revenue k" / max 1, b "cost usd" , 1 del b "latency weighted" out.append dict b return out def make status rows table rows : counts = Counter r "state" for r in table rows return {"state": k, "count": v} for k, v in counts.items def make pipeline rows table rows : counts = Counter r "pipeline" for r in table rows return {"pipeline": k, "count": v} for k, v in counts.most common def make kpis region, daily rows, table rows : runs count = sum r "runs" for r in daily rows failures = sum r "failures" for r in daily rows late = sum r "late" for r in daily rows cost = sum r "cost usd" for r in daily rows revenue = sum r "revenue k" for r in daily rows return { "region": region, "runs": runs count, "success rate": round 100 runs count - failures - late 0.35 / max 1, runs count , 1 , "avg latency": round sum r "avg latency" r "runs" for r in daily rows / max 1, runs count , 1 , "cost usd": round cost, 2 , "revenue k": round revenue, 1 , "roi": round revenue / max 1, cost , 1 , "open issues": len table rows , "p0p1": sum 1 for r in table rows if r "priority" in {"P0", "P1"} , "failure rate": round 100 failures / max 1, runs count , 2 , "spark": r "success rate" for r in daily rows -14: , } DAILY BY REGION = {"All": aggregate daily daily region rows } REGION ROWS = aggregate regions daily region rows for region in REGIONS 1: : DAILY BY REGION region = r for r in daily region rows if r "region" == region RUNS BY REGION = { region: r for r in runs if region == "All" or r "region" == region for region in REGIONS } STATUS BY REGION = { region: make status rows RUNS BY REGION region for region in REGIONS } PIPELINE BY REGION = { region: make pipeline rows RUNS BY REGION region for region in REGIONS } KPI BY REGION = { region: make kpis region, DAILY BY REGION region , RUNS BY REGION region for region in REGIONS } WATCHLIST = sorted runs, key=lambda r: r "priority" , r "state" = "Failed", -r "sla gap" :8 SCATTER ROWS = { "region": r "region" , "success rate": r "success rate" , "cost usd": r "cost usd" , "avg latency": r "avg latency" , } for r in REGION ROWS RADAR ROWS = {"metric": "Success", {r "region" : r "success rate" for r in REGION ROWS}}, {"metric": "ROI", {r "region" : min 100, r "roi" 8 for r in REGION ROWS}}, {"metric": "Latency", {r "region" : max 0, 100 - r "avg latency" / 2 for r in REGION ROWS}}, {"metric": "Cost", {r "region" : max 0, 100 - r "cost usd" / 20 for r in REGION ROWS}}, REGION ACTIONS = { region: SetState "selected region", region , SetState "line rows", DAILY BY REGION region , SetState "table rows", RUNS BY REGION region , SetState "status rows", STATUS BY REGION region , SetState "pipeline rows", PIPELINE BY REGION region , SetState "region kpis", KPI BY REGION region , SetState "selected run", None , ShowToast f"Region set to {region}", variant="info", duration=1800 , for region in REGIONS } ''' We add the main imports for Prefab components, actions, charts, control flow, and reactive state handling. We generate realistic synthetic operations data across regions, pipelines, owners, states, priorities, costs, latency, and revenue. We also create aggregation functions and prepare datasets that power the dashboard’s charts, metrics, filters, tables, and watchlist. APP CODE += r''' Building the Overview Tab initial state = { "selected region": "All", "line rows": DAILY BY REGION "All" , "table rows": RUNS BY REGION "All" , "status rows": STATUS BY REGION "All" , "pipeline rows": PIPELINE BY REGION "All" , "region kpis": KPI BY REGION "All" , "selected run": None, "slo target": 94, "operator name": "Colab Builder", "notes": {"note": "Click a run row to inspect it, then add triage notes here."} , "watchlist": WATCHLIST, "compact": False, "dark mode": False, } with PrefabApp title="Prefab Advanced Colab Tutorial", state=initial state, css class="max-w-7xl mx-auto p-6", as app: with Column gap=6 : with Row justify="between", align="center", gap=4 : with Column gap=1 : H2 "Prefab Advanced Operations Dashboard" Muted "A complete Colab-ready tutorial: Python DSL, reactive state, " "charts, tables, forms, conditionals, actions, Mermaid, and static export." with Row gap=2, align="center" : Badge f"Region: {STATE.selected region}", variant="info" Badge f"Operator: {STATE.operator name}", variant="secondary" Button "Docs", variant="outline", onClick=OpenLink "https://prefab.prefect.io/docs/welcome" , with Alert variant="info", icon="sparkles" : AlertTitle "What this app demonstrates" AlertDescription "Everything below is composed in Python. The exported HTML runs client-side " "with no backend, while actions update Prefab state instantly." with Card : with CardHeader : CardTitle "Interactive controls" CardDescription "Use state actions to swap datasets, adjust SLO targets, and personalize " "the UI without JavaScript." with CardContent : with Grid columns={"sm": 1, "md": 2, "lg": 4}, gap=4 : with Column gap=2 : Small "Region quick filters" with Row gap=2 : for region in REGIONS: Button region, size="sm", variant="secondary" if region = "All" else "default", onClick=REGION ACTIONS region , with Column gap=2 : Small "SLO target" Slider value=STATE.slo target, min=80, max=99, step=0.5, onChange=SetState "slo target", EVENT , gradient=True, Muted f"Target is {STATE.slo target}% successful runs" with Column gap=2 : Small "Operator name" Input value=STATE.operator name, placeholder="Your name", on change=SetState "operator name", EVENT , with Column gap=2 : Small "Client-side toggles" Switch value=STATE.compact, label="Compact review mode", onChange=ToggleState "compact" , Switch value=STATE.dark mode, label="Dark-mode flag", onChange=ToggleState "dark mode" , with Tabs value="overview" : with Tab "Overview", value="overview" : with Column gap=5 : with Grid columns={"sm": 1, "md": 2, "lg": 4}, gap=4 : Metric label="Runs", value=STATE.region kpis.runs, description=f"Selected slice: {STATE.selected region}", Metric label="Success rate", value=f"{STATE.region kpis.success rate}%", delta=f"Target {STATE.slo target}%", trend="up", trendSentiment="positive", Metric label="Avg latency", value=f"{STATE.region kpis.avg latency}s", description="Weighted 30-day average", Metric label="ROI index", value=STATE.region kpis.roi, description="Revenue thousands per USD cost", with Grid columns={"sm": 1, "lg": 3}, gap=4 : with Card css class="lg:col-span-2" : with CardHeader : CardTitle "Reliability trend" CardDescription "Line chart bound to a reactive state key. " "Region buttons replace the whole data list." with CardContent : LineChart data=STATE.line rows, xAxis="date", series= ChartSeries dataKey="success rate", label="Success %" , ChartSeries dataKey="avg latency", label="Avg latency" , , height=330, curve="smooth", showDots=False, showLegend=True, with Card : with CardHeader : CardTitle "SLO health" CardDescription "Progress and ring components read live KPI and target state." with CardContent : with Column gap=4, align="center" : Ring value=STATE.region kpis.success rate, target=STATE.slo target, label=f"{STATE.region kpis.success rate}%", size="lg", gradient=True, Progress value=STATE.region kpis.success rate, target=STATE.slo target, max=100, gradient=True, Sparkline data=STATE.region kpis.spark, height=56, curve="smooth", fill=True, with If STATE.region kpis.success rate = STATE.slo target : Badge "Meeting target", variant="success" with Else : Badge "Below target", variant="warning" with Grid columns={"sm": 1, "lg": 2}, gap=4 : with Card : with CardHeader : CardTitle "Open issue status mix" with CardContent : PieChart data=STATE.status rows, dataKey="count", nameKey="state", innerRadius=55, showLegend=True, height=310, with Card : with CardHeader : CardTitle "Affected pipelines" with CardContent : BarChart data=STATE.pipeline rows, xAxis="pipeline", series= ChartSeries dataKey="count", label="Open issues" , height=310, showLegend=False, ''' We define the initial application state and start building the Prefab dashboard layout. We create the dashboard header, region filters, SLO slider, operator input, toggles, and the main overview tab. We connect reactive state to metrics, line charts, progress indicators, rings, sparklines, pie charts, and bar charts so the interface updates dynamically. APP CODE += r''' Run Explorer and Diagnostics with Tab "Run Explorer", value="runs" : with Column gap=4 : with Card : with CardHeader : CardTitle "Searchable sortable run table" CardDescription "Click any row. Prefab stores the row object in state and " "conditionally renders the detail panel." with CardContent : DataTable columns= DataTableColumn key="run id", header="Run ID", sortable=True , DataTableColumn key="date", header="Date", sortable=True , DataTableColumn key="pipeline", header="Pipeline", sortable=True , DataTableColumn key="owner", header="Owner", sortable=True , DataTableColumn key="region", header="Region", sortable=True , DataTableColumn key="state", header="State", sortable=True , DataTableColumn key="priority", header="Priority", sortable=True , DataTableColumn key="duration s", header="Duration", sortable=True, align="right", , DataTableColumn key="cost usd", header="Cost", sortable=True, align="right", , , rows=STATE.table rows, search=True, paginated=True, pageSize=12, onRowClick=SetState "selected run", EVENT , with If STATE.selected run : with Card : with CardHeader : CardTitle f"Selected run: {STATE.selected run.run id}" CardDescription f"{STATE.selected run.pipeline} in {STATE.selected run.region}" with CardContent : with Grid columns={"sm": 1, "md": 4}, gap=3 : Metric label="State", value=STATE.selected run.state Metric label="Priority", value=STATE.selected run.priority Metric label="Duration", value=f"{STATE.selected run.duration s}s", Metric label="SLA gap", value=f"{STATE.selected run.sla gap} min", with CardFooter : Button "Clear selection", variant="outline", onClick=SetState "selected run", None , with Tab "Diagnostics", value="diagnostics" : with Grid columns={"sm": 1, "lg": 2}, gap=4 : with Card : with CardHeader : CardTitle "Regional reliability map" CardDescription "Scatter chart compares cost, latency, and success by region." with CardContent : ScatterChart data=SCATTER ROWS, xAxis="cost usd", yAxis="success rate", zAxis="avg latency", series= ChartSeries dataKey="region", label="Region" , height=320, with Card : with CardHeader : CardTitle "Balanced score radar" CardDescription "Scores are normalized for visual comparison across regions." with CardContent : RadarChart data=RADAR ROWS, axisKey="metric", series= ChartSeries dataKey="APAC" , ChartSeries dataKey="EMEA" , ChartSeries dataKey="NA" , ChartSeries dataKey="LATAM" , , height=320, with Card css class="lg:col-span-2" : with CardHeader : CardTitle "P0/P1 watchlist" CardDescription "ForEach repeats a mini-card for each high-priority item." with CardContent : with Grid columns={"sm": 1, "md": 2, "lg": 4}, gap=3 : with ForEach "watchlist" as item: with Card : with CardHeader : with Row justify="between", align="center" : CardTitle item.run id Badge item.priority, variant="destructive" CardDescription item.pipeline with CardContent : Text f"{item.region} · {item.state} · " f"gap {item.sla gap} min" ''' We add the run explorer and diagnostics sections of the application. We build a searchable and sortable data table where we can click a row and inspect detailed run information using conditional rendering. We also add diagnostic visualizations such as scatter charts, radar charts, and a high-priority watchlist rendered through repeated Prefab cards. APP CODE += r''' Exporting Static HTML Dashboard with Tab "Triage Notes", value="notes" : with Grid columns={"sm": 1, "lg": 2}, gap=4 : with Card : with CardHeader : CardTitle "Add client-side notes" CardDescription "The form appends submitted values to a state list. " "No backend is required." with CardContent : with Form on submit= AppendState "notes", EVENT , ShowToast "Note added", variant="success" , : Input name="note", placeholder="Example: Escalate APAC LLM Triage failures", required=True, Button "Add note", buttonType="submit" with CardFooter : Button "Remove latest note", variant="outline", onClick=PopState "notes", -1 , with Card : with CardHeader : CardTitle "Notes in state" CardDescription "ForEach renders each submitted note." with CardContent : with Column gap=2 : with ForEach "notes" as note: with Alert variant="success", icon="check" : AlertDescription note.note with Tab "Architecture", value="architecture" : with Column gap=4 : Markdown """ How this tutorial maps to real Prefab work 1. Build data in Python. 2. Compose UI components with with blocks. 3. Bind data and labels to reactive state using STATE / Rx . 4. Trigger SetState , AppendState , ToggleState , and ShowToast from UI events. 5. Export to HTML for static sharing, or serve locally with prefab serve . """ Mermaid """ flowchart LR A Python data and business logic -- B Prefab component tree B -- C JSON wire protocol C -- D Bundled React renderer D -- E Interactive dashboard E -- F Client-side state actions F -- B """ Code """prefab export /content/prefab advanced tutorial app.py -o /content/prefab advanced dashboard.html prefab serve /content/prefab advanced tutorial app.py --reload""", language="bash", ''' APP PATH.write text APP CODE print "Verifying Prefab installation..." subprocess.check call sys.executable, "-m", "prefab ui.cli", "version" print "\nExporting Prefab app to static HTML..." subprocess.check call sys.executable, "-m", "prefab ui.cli", "export", str APP PATH , "-o", str HTML PATH , print f"\nPython app written to: {APP PATH}" print f"Static dashboard written to: {HTML PATH}" print "\nDownload links:" display FileLink str APP PATH display FileLink str HTML PATH html bytes = HTML PATH.read bytes encoded = base64.b64encode html bytes .decode "utf-8" display HTML f"""