Stop Wrestling with HTML: Why I Switched to Flet
I am tired of pretending that I enjoy writing HTML and CSS. For years, every time I needed to build a user interface for a Python script or a data tool, I faced a miserable choice: build a hideous Tkinter window that looks like it belongs in Windows 95, or spend three days setting up a React frontend just to display a few charts and buttons. It felt like there was no middle ground for developers who just want to ship logic without becoming full-stack engineers.
Then I found Flet ui. It didn’t just solve the problem; it completely realigned how I think about building applications in Python. If you are a backend developer, a data scientist, or just someone who prefers logic over markup, this is the framework that finally speaks your language.
The “Code-First” Mental Model
The biggest friction point with most web frameworks is the context switch. You write your business logic in Python, but your UI in HTML, your styling in CSS, and your interactivity in JavaScript. You are constantly translating between languages and paradigms. Even with tools like Django templates, you are still mentally juggling markup and logic.
Flet throws that out the window. In Flet, the UI is Python code. You don’t write a <div>; you instantiate a Container class. You don’t write an onclick attribute in a string; you pass a Python function to the on_click argument. This sounds like a small syntax change, but the impact on my workflow has been massive.
Here is why this matters: I can use all my standard Python tooling on my UI code. Type hints work perfectly, so my IDE tells me exactly what properties a button accepts. I can run the Ruff linter to catch errors in my layout logic before I even run the app. I don’t need a separate build chain for the frontend because there isn’t one.
Building a Quick Data Dashboard
Let me show you exactly what I mean. I recently needed to build a quick dashboard to monitor some server logs and visualize data. In the past, I might have reached for Streamlit, but I hit a wall when I needed custom layout control. Streamlit is great for linear scripts, but Flet gives you the compositional power of a real UI framework like Flutter (which it is based on) without the Dart learning curve.
I use the Uv installer for my projects now because it is blazingly fast, so setting up a Flet environment takes seconds. Here is a simplified version of a dashboard structure I use frequently. It demonstrates how you can mix layout (Rows/Columns) with logic instantly.

import flet as ft
import datetime
def main(page: ft.Page):
page.title = "Server Monitor 2025"
page.theme_mode = ft.ThemeMode.DARK
page.padding = 20
# State variable for our log list
log_list = ft.ListView(expand=1, spacing=10, padding=20, auto_scroll=True)
def add_log(e):
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
# Create a visual tile for the log entry
new_tile = ft.Container(
content=ft.Row([
ft.Icon(ft.icons.INFO_OUTLINE, color=ft.colors.BLUE_400),
ft.Text(f"[{timestamp}] System check initiated...", size=14),
]),
bgcolor=ft.colors.GREY_900,
padding=10,
border_radius=5,
)
log_list.controls.append(new_tile)
page.update()
# Layout: A sidebar and a main content area
page.add(
ft.Row(
[
# Sidebar
ft.Container(
width=250,
bgcolor=ft.colors.GREY_800,
border_radius=10,
padding=20,
content=ft.Column([
ft.Text("CONTROLS", weight="bold", size=20),
ft.Divider(),
ft.ElevatedButton(
"Run Diagnostics",
icon=ft.icons.PLAY_ARROW,
on_click=add_log,
width=200
),
ft.ElevatedButton(
"Clear Logs",
icon=ft.icons.DELETE,
on_click=lambda _: log_list.controls.clear() or page.update(),
width=200,
bgcolor=ft.colors.RED_700,
color=ft.colors.WHITE
)
])
),
# Main Content
ft.Container(
expand=True,
bgcolor=ft.colors.BLACK,
border_radius=10,
content=log_list
)
],
expand=True
)
)
ft.app(target=main)
Notice the page.update() call? That is the magic. I modify the Python object list log_list.controls, call update, and the UI reflects the change. I didn’t have to think about the DOM, virtual DOM diffing, or reactive state hooks. I just manipulated a list.
State Management That Makes Sense
One of the things that drives me crazy about modern frontend development is the complexity of state management. Redux, Context API, Signals—it feels like over-engineering for 90% of internal tools. In Flet, state is just… variables. Since the code runs on the backend (or locally), you don’t need to serialize everything to JSON to send it to the client and back just to update a counter.
For more complex apps, I usually pair Flet with Pydantic (using Type hints heavily) to structure my data. If I’m dealing with large datasets, say from a Polars dataframe, I can filter and aggregate that data in Python and just push the results to a Flet DataTable. I don’t have to worry about sending 100MB of JSON to the browser because Flet handles the communication efficiently via WebSockets.
This approach does have a trade-off: latency. Because the logic runs on the server, every interaction is a round trip. However, for internal business apps, admin panels, or data tools, this is negligible. If you are building a twitch-reflex game, you shouldn’t be using Python anyway. But for a dashboard where I click “Process” and wait for a FastAPI backend to crunch numbers, it feels instantaneous.
The Ecosystem Comparison
I often get asked how this compares to other options like Reflex app (formerly Pynecone) or Taipy news. I have tried them all. Reflex is fantastic and compiles to React, which is great for performance, but I find the compilation step sometimes slows down my “tinker-test” loop. Taipy is powerful for pipeline orchestration but feels a bit more rigid for custom UI layouts.
Flet sits in a sweet spot. It feels imperative. I write code that says “do this, then show that.” It matches how I write scripts. It also plays nice with the broader ecosystem. I’ve been experimenting with PyScript web for running Python entirely in the browser, and while that is exciting for the future, Flet is the “get it done now” tool for 2025.
Another massive benefit is packaging. Using the Rye manager or standard pip tools, I can bundle a Flet app into a standalone executable. I can send a .exe or .app file to a colleague who doesn’t have Python installed, and it just works. They don’t need to know I used Pandas updates or specific libraries; it’s all contained.
Integrating Modern Python Features

Since Flet is just Python, I treat it like any other project. I use MyPy updates to ensure my component structure is sound. I’ve even started integrating some Local LLM capabilities into my Flet apps. For instance, I built a small chat interface that talks to a locally running Llama model. Because Flet supports asynchronous functions natively, handling the streaming response from the LLM was trivial.
Here is a snippet showing how easy async works in Flet, which is critical when you are doing blocking operations like querying a database or calling an API.
import flet as ft
import asyncio
async def main(page: ft.Page):
status_text = ft.Text("Ready")
async def run_heavy_task(e):
status_text.value = "Processing..."
page.update()
# Simulate a heavy blocking IO operation
await asyncio.sleep(2)
status_text.value = "Task Complete!"
status_text.color = ft.colors.GREEN
page.update()
page.add(
ft.ElevatedButton("Start Job", on_click=run_heavy_task),
status_text
)
ft.app(target=main)
This asynchronous capability is vital. I see so many Tkinter apps freeze up because the developer put a time.sleep() in the main thread. Flet encourages you to use async/await, which aligns perfectly with modern libraries like FastAPI or Playwright python if you are building automation tools with a UI.
Where Flet Struggles
I want to be honest about the limitations. Flet is not the right tool if you need pixel-perfect, SEO-optimized consumer websites. It renders to a canvas-like structure (via Flutter) or dynamic DOM elements that aren’t always semantic HTML. If you are building the next e-commerce giant, stick to Next.js or Django templates.

Also, debugging layout issues can sometimes be tricky. Since you are abstracting the CSS, when things don’t align correctly, you can’t just “Inspect Element” and tweak CSS rules as easily as you would in a standard web app. You have to adjust the Python properties, restart, and check. However, the hot-reload feature in Flet is generally fast enough that this isn’t a dealbreaker for me.
Why This Matters for Python Devs
The Python ecosystem is evolving rapidly. We have Mojo language promising speed, DuckDB python revolutionizing local analytics, and LangChain updates changing how we interact with AI. But the UI layer has always been the lagging component. We had great engines but terrible dashboards.
Flet bridges that gap. It allows me to build tools that look professional without needing a second team member to handle the frontend. I can build a complex data entry form, a visualization dashboard using Matplotlib or Altair charts (which Flet supports), or a control panel for my home automation scripts, all in the language I love.
If you are still writing CLI tools because you are afraid of the UI work, or if you are struggling with the boilerplate of web frameworks, I highly recommend giving Flet a weekend of your time. It might just save you from ever having to center a div again.
