Imagine writing a single Python script that runs as a sleek web app in the browser, a native desktop application on Windows or macOS, and even a mobile app on iOS and Android—all without touching HTML, CSS, or JavaScript. That’s not science fiction; that’s Flet. Born from the frustration of fragmented UI development, Flet reimagines what’s possible when you combine Python’s elegance with the universality of the web platform. This guide isn’t just a reference—it’s your companion on a journey from curious beginner to confident Flet master. We’ll explore not only how Flet works but why it matters, how it solves real-world problems, and how you can leverage it to build faster, better, and more joyfully.
Flet is an open-source framework that allows developers to build interactive, real-time user interfaces using nothing but Python. Unlike traditional web frameworks that require you to split your logic between backend (Python) and frontend (JavaScript/HTML/CSS), Flet keeps everything in one language and one file. Under the hood, Flet uses Flutter (Google’s UI toolkit) to render your app, which means you get pixel-perfect, high-performance interfaces across all platforms. But you never have to write Dart or deal with Flutter’s complexity—Flet abstracts it all away. Your Python code describes the UI structure and behavior, and Flet handles the rendering, state synchronization, and event handling automatically. This makes Flet ideal for data scientists, educators, hobbyists, and professional developers who want to ship beautiful apps without becoming full-stack web engineers.
import flet as ft
def main(page: ft.Page):
page.title = "Flet: One Language, Every Platform"
page.add(
ft.Text("This app runs identically on web, desktop, and mobile!", size=20, weight="bold"),
ft.Divider(),
ft.Text("No HTML. No CSS. No JavaScript. Just pure Python.", italic=True)
)
ft.app(target=main)
Let’s be honest: building GUIs in Python has historically been painful. Tkinter feels archaic, PyQt is powerful but complex, and web frameworks force you into the JavaScript ecosystem. Flet eliminates these trade-offs. It gives you modern, responsive UI components out of the box (buttons, cards, lists, charts), real-time collaboration features, and seamless deployment to the cloud—all while letting you stay in your Python comfort zone. If you’ve ever spent hours debugging a CSS layout or wrestling with Electron’s memory usage, Flet will feel like liberation. Moreover, because Flet apps are inherently stateful and reactive, you avoid the mental overhead of managing DOM updates or REST API calls for simple interactions. This isn’t just about convenience; it’s about reclaiming your time and focus for what truly matters: solving your users’ problems.
import flet as ft
def main(page: ft.Page):
page.theme_mode = "light"
page.padding = 30
page.add(
ft.Card(
content=ft.Container(
ft.Column([
ft.Text("Why Flet Wins", size=22, weight="bold", color="#2d3561"),
ft.Text("• Write once, run everywhere", size=16),
ft.Text("• Real-time updates without WebSockets boilerplate", size=16),
ft.Text("• Modern Material Design by default", size=16),
ft.Text("• Zero frontend knowledge required", size=16)
], spacing=12),
padding=25
),
elevation=3
)
)
ft.app(target=main)
Setting up Flet is refreshingly simple. There’s no need for Node.js, npm, Webpack, or complex virtual environments (though you can use them if you prefer). Just install the package with pip, and you’re ready to code. The entire development workflow is contained within Python: write your script, run it, and Flet launches a local server that serves your app in a browser window. Changes you make to your code can be seen instantly with auto-reload in development mode. This frictionless onboarding means you can go from zero to a working prototype in under five minutes—a game-changer for rapid iteration and experimentation. And because Flet is pure Python, your existing tools (IDEs, linters, debuggers) work perfectly.
# Install Flet (run in terminal)
pip install flet
# Then create your first app: hello_flet.py
import flet as ft
def main(page: ft.Page):
page.add(ft.Text("Hello, Flet Universe! 🌍"))
ft.app(target=main)
# Run it:
# python hello_flet.py
Every Flet application follows a simple but powerful pattern. At its heart is the main function, which
receives a Page object as its argument. This Page represents the user’s window or tab—it’s
your canvas. You populate it by adding Control objects (like Text, Button,
TextField) either directly or within layout containers like Column and Row.
The ft.app(target=main) call kicks everything off: in web mode, it starts a local HTTP server; in
desktop mode, it bundles a Chromium instance. What’s elegant is how Flet mirrors the structure of your UI in
code—nesting controls in Python directly translates to nesting elements in the rendered DOM. This declarative
approach makes your code self-documenting and easy to reason about.
import flet as ft
def main(page: ft.Page):
# Page configuration
page.title = "App Anatomy"
page.scroll = "adaptive"
# UI Controls
header = ft.Text("Understanding the Structure", size=24, weight="bold")
explanation = ft.Text(
"The Page is your root container. Add Controls to it to build your UI.",
color="#4a5568"
)
button = ft.ElevatedButton("Explore Further", on_click=lambda e: print("Navigating..."))
# Assemble the view
page.add(header, ft.Divider(), explanation, button)
ft.app(target=main)
The Page object is far more than just a container—it’s the central nervous system of your Flet app.
Through it, you control everything from window dimensions and theme mode to navigation, overlays (like dialogs), and
even low-level WebSocket communication in web deployments. You can set properties like page.title,
page.bgcolor, or page.window_width to customize the user experience. More importantly, the
Page manages the lifecycle of your UI: when you call page.add(),
page.update(), or page.clean(), you’re instructing Flet how to synchronize your Python
state with what the user sees. In multi-user scenarios (like Flet Cloud apps), each connected client gets its own
isolated Page instance, enabling true real-time collaboration without extra effort.
import flet as ft
def main(page: ft.Page):
# Configure the page
page.title = "Page Command Center"
page.theme_mode = "dark"
page.window_width = 900
page.window_height = 600
page.window_resizable = True
page.bgcolor = "#1a202c"
# Display current settings
info = ft.Column([
ft.Text(f"Title: {page.title}", color="white"),
ft.Text(f"Theme: {page.theme_mode}", color="white"),
ft.Text(f"Size: {page.window_width}x{page.window_height}", color="white")
])
page.add(info)
ft.app(target=main)
Flet ships with a rich library of ready-to-use UI controls that follow Google’s Material Design guidelines. From
basic elements like Text and Icon to interactive components like
ElevatedButton, TextField, Slider, and Switch, you have
everything needed to create professional interfaces. Each control is highly customizable—change colors, sizes,
icons, tooltips, and behaviors with simple properties. What’s more, these controls are not just visual; they’re
fully interactive and accessible by default, supporting keyboard navigation and screen readers. This means you can
build inclusive, user-friendly apps without extra work. The key philosophy is composability: combine simple controls
into complex ones, and reuse them across your application.
import flet as ft
def main(page: ft.Page):
page.add(
ft.Text("Essential Controls Demo", size=22, weight="bold"),
ft.Divider(),
ft.Row([
ft.IconButton(ft.icons.HOME, icon_size=30),
ft.TextField(label="Search", hint_text="Type here...", width=200),
ft.Switch(label="Dark Mode"),
ft.Slider(min=0, max=100, value=50, width=200),
ft.ElevatedButton("Action", icon=ft.icons.PLAY_ARROW)
], alignment="center")
)
ft.app(target=main)
Creating beautiful, responsive layouts in Flet is intuitive thanks to its flexbox-inspired system. The
Column control stacks its children vertically, while Row arranges them horizontally. Both
support alignment, spacing, and expansion properties that give you pixel-perfect control over positioning. Wrap any
control in a Container to add padding, margin, borders, shadows, or background colors—think of it as a
versatile wrapper that enhances visual hierarchy. For more complex grids, nest Column and
Row freely. The magic lies in the expand property: set it to True or a
numeric ratio to make controls fill available space proportionally. This system eliminates the need for fragile CSS
hacks and makes your layouts adapt gracefully to any screen size.
import flet as ft
def main(page: ft.Page):
page.add(
ft.Text("Responsive Layout Example", size=20, weight="bold"),
ft.Column([
ft.Container(
content=ft.Text("Header", weight="bold"),
bgcolor="#4299e1",
padding=15,
alignment=ft.alignment.center
),
ft.Row([
ft.Container(
content=ft.Text("Sidebar
Navigation"),
bgcolor="#e2e8f0",
width=200,
padding=15
),
ft.Container(
content=ft.Column([
ft.Text("Main Content Area", size=18, weight="bold"),
ft.Text("This section expands to fill remaining space.", italic=True)
], spacing=10),
expand=True,
padding=20,
bgcolor="white"
)
], expand=True),
ft.Container(
content=ft.Text("Footer", text_align="center"),
bgcolor="#cbd5e0",
padding=10
)
], expand=True)
)
ft.app(target=main)
In today’s multi-device world, responsive design isn’t optional—it’s essential. Flet handles responsiveness
automatically through its layout engine, but you can take it further with programmatic control. The
Page object exposes page.width and page.height, which update in real time as
the window resizes. You can listen to resize events via page.on_resize and adjust your UI
dynamically—show/hide sidebars, switch between grid and list views, or change font sizes. Additionally, Flet’s
scroll="adaptive" property ensures content remains accessible on small screens by enabling scrolling
only when necessary. This means your app looks and works great on a 4K monitor, a laptop, a tablet, or a
smartphone—without writing separate code for each.
import flet as ft
def main(page: ft.Page):
status = ft.Text("", size=18, weight="bold")
def on_resize(e):
status.value = f"Screen: {page.width} × {page.height}px"
# Dynamically adjust layout based on width
if page.width < 600:
status.color = "red"
elif page.width < 1000:
status.color = "orange"
else:
status.color = "green"
page.update()
page.on_resize = on_resize
page.add(
ft.Text("Resize this window to see responsive behavior!", size=20),
status
)
# Trigger initial update
on_resize(None)
ft.app(target=main)
Branding and visual identity matter. Flet makes it easy to customize the look and feel of your entire app through
theming. You can define a global Theme object that controls colors, typography, shapes, and component
defaults. Switch between light and dark mode globally with page.theme_mode, or override styles per
control using properties like bgcolor, color, and font_family. For
pixel-perfect designs, use Container to add shadows, borders, and gradients. The best part? All styling
is done in Python—no CSS files to manage, no class name conflicts, and no context switching. This keeps your design
system consistent and maintainable, especially as your app grows.
import flet as ft
def main(page: ft.Page):
# Define a custom theme
page.theme = ft.Theme(
color_scheme=ft.ColorScheme(
primary="#8b5cf6", # Vibrant purple
secondary="#ec4899", # Pink accent
surface="#f3f4f6"
),
font_family="Roboto",
use_material3=True
)
page.theme_mode = "light"
page.add(
ft.ElevatedButton("Primary Action", icon=ft.icons.STAR),
ft.OutlinedButton("Secondary Action", icon=ft.icons.FAVORITE_BORDER),
ft.Text("Styled with custom theme!", size=18, weight="bold")
)
ft.app(target=main)
Static UIs are boring—interactivity is where apps shine. Flet’s event system is straightforward and powerful. Every
interactive control (buttons, inputs, sliders) exposes event handlers like on_click,
on_change, or on_submit. Attach a Python function to these, and it will be called whenever
the user triggers the event. The event object passed to your handler contains useful context: which control fired
it, what the new value is, and even keyboard modifiers. This lets you build rich interactions—form validation,
real-time previews, undo/redo stacks—with minimal code. Because everything runs in Python, you have full access to
your app’s state and logic without async/callback hell.
import flet as ft
def main(page: ft.Page):
click_count = ft.Text("Clicked: 0 times", size=18)
counter = 0
def on_click(e):
nonlocal counter
counter += 1
click_count.value = f"Clicked: {counter} times"
# Change button color based on count
btn.bgcolor = "#48bb78" if counter % 2 == 0 else "#f56565"
page.update()
btn = ft.ElevatedButton("Click Me!", on_click=on_click, bgcolor="#3182ce")
page.add(click_count, btn)
ft.app(target=main)
Displaying collections of data—messages, products, settings—is a common requirement. Flet’s ListView
is optimized for performance, even with thousands of items, thanks to virtualization (only visible items are
rendered). You can add, remove, or update items at runtime by modifying the controls list and calling
page.update(). For more structure, use ListTile with leading/trailing icons, subtitles,
and dense layouts. Combine with Divider or custom padding to create visually appealing lists. This
pattern is perfect for chat interfaces, dashboards, or any feed-style content where data changes frequently.
import flet as ft
import random
def main(page: ft.Page):
messages = ft.ListView(spacing=10, padding=20, auto_scroll=True, height=300)
def send_message(e):
user_msg = ft.ListTile(
title=ft.Text("You", weight="bold"),
subtitle=ft.Text(input_field.value),
leading=ft.Icon(ft.icons.ACCOUNT_CIRCLE, color="blue")
)
bot_msg = ft.ListTile(
title=ft.Text("Bot", weight="bold"),
subtitle=ft.Text(random.choice(["Got it!", "Interesting.", "Tell me more!", "👍"])),
leading=ft.Icon(ft.icons.SUPPORT_AGENT, color="green")
)
messages.controls.extend([user_msg, bot_msg, ft.Divider()])
input_field.value = ""
page.update()
input_field = ft.TextField(
hint_text="Type a message...",
on_submit=send_message,
expand=True
)
send_btn = ft.IconButton(ft.icons.SEND, on_click=send_message)
page.add(
ft.Text("Chat Interface Demo", size=20, weight="bold"),
messages,
ft.Row([input_field, send_btn])
)
ft.app(target=main)
As your app grows, you’ll need multiple views—dashboard, settings, profile, etc. Flet’s routing system makes this
seamless. Define routes like /, /settings, or /user/123, and use
page.go(route) to navigate. Manage your view stack with page.views: clear it and add a new
View object containing your UI for that route. Each View can have its own
AppBar, floating action button, or bottom navigation. The page.on_route_change callback
lets you handle route transitions, fetch data, or update the UI. This SPA (Single Page Application) approach keeps
your app fast and fluid, with no full-page reloads.
import flet as ft
def main(page: ft.Page):
def route_change(e):
page.views.clear()
if page.route == "/":
page.views.append(
ft.View(
"/",
[
ft.AppBar(title=ft.Text("Home"), bgcolor="#4a5568"),
ft.Text("Welcome to the Home Page!", size=24),
ft.ElevatedButton("Go to Settings", on_click=lambda _: page.go("/settings"))
]
)
)
elif page.route == "/settings":
page.views.append(
ft.View(
"/settings",
[
ft.AppBar(title=ft.Text("Settings"), bgcolor="#2d3436"),
ft.Switch(label="Enable Notifications", value=True),
ft.ElevatedButton("Back Home", on_click=lambda _: page.go("/"))
]
)
)
page.update()
page.on_route_change = route_change
page.go(page.route) # Initialize with current route
ft.app(target=main)
Forms are the primary way users interact with your app, so they must be intuitive and robust. Flet provides all the
form controls you need: TextField (with password masking, icons, and helper text),
Dropdown, Checkbox, RadioGroup, and more. Validation is straightforward:
check values in your submit handler, display errors using error_text on fields, or show global messages
with SnackBar. For complex forms, group related fields in Column or Card
containers. Remember to handle edge cases like empty submissions or invalid formats—your users will thank you for
the clear feedback.
import flet as ft
def main(page: ft.Page):
name = ft.TextField(label="Full Name", width=300)
email = ft.TextField(label="Email", width=300)
age = ft.TextField(label="Age", keyboard_type="number", width=300)
def submit_form(e):
errors = []
if not name.value:
errors.append("Name is required")
if "@" not in email.value:
errors.append("Valid email required")
if not age.value.isdigit() or int(age.value) < 18:
errors.append("Age must be 18+")
if errors:
page.snack_bar = ft.SnackBar(
ft.Text("Fix errors:
" + "\n".join(errors)),
bgcolor="#e53e3e",
open=True
)
else:
page.snack_bar = ft.SnackBar(
ft.Text("Form submitted successfully!"),
bgcolor="#38a169",
open=True
)
page.update()
page.add(
ft.Text("User Registration Form", size=22, weight="bold"),
name, email, age,
ft.ElevatedButton("Submit", on_click=submit_form, width=300)
)
ft.app(target=main)
Sometimes you need the user’s undivided attention—for confirmations, alerts, or quick inputs. Flet’s overlay system
handles this elegantly. Use AlertDialog for modal dialogs with custom content and actions, or
SnackBar for temporary, non-blocking messages at the bottom of the screen. Both are added to the
page.overlay list and shown with page.open(). They appear above your main UI without
disrupting the underlying layout, and they automatically handle focus and accessibility. This pattern is perfect for
delete confirmations, success notifications, or login prompts—keeping the user in context while demanding a
decision.
import flet as ft
def main(page: ft.Page):
def show_confirmation(e):
dlg = ft.AlertDialog(
title=ft.Text("Delete Account?"),
content=ft.Text("This action cannot be undone. All your data will be permanently deleted."),
actions=[
ft.TextButton("Cancel", on_click=lambda e: page.close(dlg)),
ft.TextButton(
"Delete",
on_click=lambda e: (page.close(dlg), page.add(ft.Text("Account deleted!"))),
style=ft.ButtonStyle(color="#e53e3e")
)
],
actions_alignment="end"
)
page.open(dlg)
page.add(
ft.Text("Danger Zone", size=20, weight="bold", color="#e53e3e"),
ft.ElevatedButton("Delete Account", on_click=show_confirmation, bgcolor="#e53e3e", color="white")
)
ft.app(target=main)
Visual elements like icons and images make your UI more engaging and intuitive. Flet includes all 2,000+ Material
Icons out of the box—you reference them by name (e.g., ft.icons.STAR) and customize size/color
instantly. For images, use the Image control with local paths or URLs; it supports resizing, cropping,
and placeholders. Both icons and images are accessible: add tooltip or semantics_label for
screen readers. Use them sparingly but purposefully—to indicate actions (trash can for delete), show status
(checkmark for success), or add personality (user avatars). This visual language reduces cognitive load and makes
your app feel polished.
import flet as ft
def main(page: ft.Page):
page.add(
ft.Text("Visual Elements Gallery", size=22, weight="bold"),
ft.Row([
ft.Icon(ft.icons.FAVORITE, color="red", size=40),
ft.Icon(ft.icons.STAR, color="amber", size=40),
ft.Icon(ft.icons.THUMB_UP, color="green", size=40)
], alignment="center"),
ft.Image(
src="https://picsum.photos/300/200",
width=300,
height=200,
fit=ft.ImageFit.COVER,
border_radius=10,
tooltip="Random image from Picsum"
)
)
ft.app(target=main)
Data is only valuable if it’s understandable. Flet’s built-in charting controls (LineChart,
BarChart, PieChart) let you visualize metrics without external dependencies. Define data
series with points, colors, and labels, and Flet renders them beautifully with axes, grids, and tooltips. For
advanced needs, generate charts with Matplotlib or Plotly in Python, save as PNG, and display with
Image. Either way, your dashboards become insightful at a glance—perfect for analytics, monitoring, or
reporting. Remember to keep charts simple: focus on the key message, avoid clutter, and use color intentionally to
highlight trends or outliers.
import flet as ft
def main(page: ft.Page):
sales_chart = ft.BarChart(
bar_groups=[
ft.BarChartGroup(
x=0,
bar_rods=[ft.BarChartRod(from_y=0, to_y=45, width=25, color="#4299e1")]
),
ft.BarChartGroup(
x=1,
bar_rods=[ft.BarChartRod(from_y=0, to_y=78, width=25, color="#38a169")]
),
ft.BarChartGroup(
x=2,
bar_rods=[ft.BarChartRod(from_y=0, to_y=62, width=25, color="#ed8936")]
)
],
bottom_axis=ft.ChartAxis(
labels=[
ft.ChartAxisLabel(value=0, label=ft.Text("Jan")),
ft.ChartAxisLabel(value=1, label=ft.Text("Feb")),
ft.ChartAxisLabel(value=2, label=ft.Text("Mar"))
]
),
left_axis=ft.ChartAxis(labels_size=40),
horizontal_grid_lines=ft.ChartGridLines(color="#e2e8f0", width=1),
tooltip_bgcolor="#1a202c",
width=400,
height=300
)
page.add(
ft.Text("Monthly Sales Report", size=20, weight="bold"),
sales_chart
)
ft.app(target=main)
Subtle animations transform functional apps into delightful experiences. Flet supports implicit animations out of
the box—when you change a control’s animatable property (like width, height,
opacity, bgcolor, or offset), it transitions smoothly over a specified
duration. For example, a button that gently pulses on hover, or a panel that slides in from the side. You can also
chain animations or trigger them on events. These micro-interactions provide feedback, guide attention, and make
your app feel alive. The key is restraint: animations should enhance usability, not distract from it.
import flet as ft
def main(page: ft.Page):
animated_box = ft.Container(
width=100,
height=100,
bgcolor="#4299e1",
border_radius=10,
animate_offset=ft.animation.Animation(300, "easeInOut"),
offset=(0, 0)
)
def toggle_position(e):
animated_box.offset = (1, 0) if animated_box.offset == (0, 0) else (0, 0)
page.update()
page.add(
ft.Text("Click to Animate!", size=18),
animated_box,
ft.ElevatedButton("Move Box", on_click=toggle_position)
)
ft.app(target=main)
State is the heartbeat of interactive apps—it’s the data that changes over time (user inputs, API responses,
timers). In Flet, state management is refreshingly simple because your UI is a direct reflection of your Python
variables. For small apps, local variables inside main() suffice. For larger ones, encapsulate state in
classes (e.g., a ShoppingCart class with add_item() and total() methods).
Always call page.update() after mutating state to refresh the UI. Avoid anti-patterns like storing
state in control properties—keep it in your Python logic. This separation makes your code testable, debuggable, and
scalable. Remember: Flet only sends UI diffs to the client, so frequent updates are efficient.
import flet as ft
class Counter:
def __init__(self):
self.value = 0
def increment(self):
self.value += 1
def decrement(self):
self.value -= 1
def main(page: ft.Page):
counter = Counter()
display = ft.Text(str(counter.value), size=40, weight="bold")
def update_display():
display.value = str(counter.value)
page.update()
page.add(
ft.Text("State Management Demo", size=20, weight="bold"),
display,
ft.Row([
ft.IconButton(ft.icons.REMOVE, on_click=lambda e: (counter.decrement(), update_display())),
ft.IconButton(ft.icons.ADD, on_click=lambda e: (counter.increment(), update_display()))
], alignment="center")
)
ft.app(target=main)
Real-world apps often need to work with files—import user data, export reports, or save preferences. Flet provides
FilePicker for secure client-side file operations. Use pick_files() to let users select
files (contents stay in the browser for privacy), or save_file() to trigger a download. For persistent
storage between sessions, use page.client_storage to save key-value pairs (strings, numbers, booleans)
in the user’s browser localStorage. This is perfect for settings, cached data, or user preferences. Note: for
server-side file processing, you’ll need a backend—but Flet makes the frontend part trivial.
import flet as ft
def main(page: ft.Page):
file_name = ft.Text("No file selected")
def pick_file_result(e: ft.FilePickerResultEvent):
if e.files:
file_name.value = f"Selected: {e.files[0].name}"
# Save to client storage
page.client_storage.set("last_file", e.files[0].name)
else:
file_name.value = "Selection cancelled"
page.update()
file_picker = ft.FilePicker(on_result=pick_file_result)
page.overlay.append(file_picker)
# Load last file from storage
last = page.client_storage.get("last_file")
if last:
file_name.value = f"Last used: {last}"
page.add(
ft.Text("File Handling Demo", size=20, weight="bold"),
file_name,
ft.ElevatedButton("Pick a File", on_click=lambda _: file_picker.pick_files())
)
ft.app(target=main)
One of Flet’s superpowers is built-in real-time collaboration. When you deploy your app to Flet Cloud or run it
with flet --web, multiple users can connect to the same instance, and their actions sync instantly.
This isn’t just chat—it’s shared whiteboards, live dashboards, multiplayer games, or co-editing documents. How? Flet
maintains a separate Page for each client but lets you broadcast updates to all via a shared backend
(or in-memory store for simple cases). The framework handles the WebSocket plumbing, so you focus on the logic. This
turns solo apps into social experiences with minimal extra code.
import flet as ft
from collections import defaultdict
# Shared state (in production, use Redis or database)
connected_users = defaultdict(list)
def main(page: ft.Page):
user_id = id(page) # Unique per client
messages = ft.ListView(auto_scroll=True, height=300)
# Add existing messages
for msg in connected_users["global"]:
messages.controls.append(ft.Text(msg))
def send_message(e):
msg = f"User {user_id}: {input_field.value}"
# Save to shared state
connected_users["global"].append(msg)
# Broadcast to all pages (simplified)
messages.controls.append(ft.Text(msg))
input_field.value = ""
page.update()
input_field = ft.TextField(hint_text="Type a message...", on_submit=send_message, expand=True)
page.add(
ft.Text("Real-Time Chat Room", size=20, weight="bold"),
messages,
ft.Row([input_field, ft.IconButton(ft.icons.SEND, on_click=send_message)])
)
ft.app(target=main)
Sharing your creation with the world should be effortless—and with Flet, it is. The flet publish
command deploys your app to Flet Cloud in seconds, giving you a public URL (e.g.,
https://your-app.flet.app). No servers to configure, no domains to buy, no SSL certificates to manage.
For private deployments, run flet --host 0.0.0.0 --port 8000 on any Linux server and proxy through
Nginx. Flet handles HTTPS, scaling, and session management automatically in cloud mode. This means you can go from
coding to sharing with stakeholders in under a minute—a massive boost for feedback loops and iteration speed.
# Terminal commands
# 1. Install Flet (if not done)
pip install flet
# 2. Create your app (my_app.py)
# 3. Publish to Flet Cloud
flet publish my_app.py
# Output: Your app is live at https://my-app.flet.app
# That's it! No extra steps.
Need to distribute your app as a native desktop application? Flet’s build command creates self-contained executables for Windows (.exe), macOS (.dmg), and Linux (.AppImage). It bundles a minimal Chromium runtime, Python interpreter, and your code into a single folder or installer. The result feels like a native app—no browser chrome, offline support, and OS integration (menus, notifications, etc.). This is ideal for internal tools, client deliverables, or apps that require local system access. Best of all, you use the same codebase—no platform-specific forks or conditional logic.
# Build commands (run in terminal)
# For Windows
flet build windows my_app.py
# For macOS
flet build macos my_app.py
# For Linux
flet build linux my_app.py
# Output: ./build/ folder with ready-to-distribute app
Yes, your Flet app runs on phones too! Using the same Python code, Flet compiles to native iOS and Android packages
via Flutter. Run flet build apk for Android or flet build ipa for iOS. The resulting apps
have touch-friendly UIs, access to device features (camera, GPS, sensors via plugins), and app store compliance.
While mobile builds require platform-specific tooling (Android Studio/Xcode), the Flet workflow abstracts most
complexity. This means you can reach billions of mobile users without learning Swift, Kotlin, or Dart—just Python.
# Mobile build prerequisites
# - Android: Install Android Studio, accept licenses
# - iOS: Apple Developer account, Xcode installed
# Build commands
flet build apk my_app.py # Generates my_app.apk
flet build ipa my_app.py # Generates my_app.ipa (for TestFlight/App Store)
# Note: First build may take 10-15 minutes (Flutter setup)
Flet apps often need to fetch data from or send data to external services. Since Flet runs Python, you can use any
HTTP library (requests, httpx, aiohttp) inside event handlers. Call REST
APIs, GraphQL endpoints, or WebSockets seamlessly. For databases, connect directly (SQLite, PostgreSQL) or via an
ORM like SQLAlchemy. Handle errors gracefully with try/except, and show loading states with ProgressBar
or disabled buttons. Remember: in web deployments, your Flet script runs on the server, so it has full
network access—unlike client-side JavaScript.
import flet as ft
import requests
def main(page: ft.Page):
weather_info = ft.Text("Loading weather...", size=18)
progress = ft.ProgressBar(width=200, visible=False)
def fetch_weather(e):
progress.visible = True
page.update()
try:
# Public API - no key needed
res = requests.get("https://wttr.in?format=%l:+%t+%h+%w")
weather_info.value = res.text.strip()
except Exception as ex:
weather_info.value = f"Error: {str(ex)}"
finally:
progress.visible = False
page.update()
page.add(
ft.Text("Live Weather Checker", size=20, weight="bold"),
weather_info,
progress,
ft.ElevatedButton("Get Weather", on_click=fetch_weather)
)
ft.app(target=main)
Testing is crucial for maintainable apps. Flet’s programmatic nature makes it highly testable with standard Python
tools like pytest. You can unit-test your state logic (e.g., a Counter class) without any
UI. For integration tests, simulate user interactions by calling event handlers directly and asserting UI state.
While end-to-end tests (with Selenium) are possible, they’re often unnecessary—Flet’s reactive model means if your
state is correct, your UI will be too. Start with simple tests for critical paths (login, form submission), and
expand as your app grows. This catches regressions early and builds confidence in refactoring.
# test_weather_app.py
import pytest
from unittest.mock import patch
import requests
# Example: Test the weather fetching logic
def test_weather_fetch_success():
with patch('requests.get') as mock_get:
mock_get.return_value.text = "London: +15°C 60% 🌦"
# Call your fetch function here
assert True # Simplified for example
# Example: Test counter logic
class Counter:
def __init__(self):
self.value = 0
def increment(self):
self.value += 1
def test_counter():
c = Counter()
c.increment()
assert c.value == 1
Even the best-designed apps can feel sluggish if not optimized. Flet is efficient by default (sending only UI
diffs), but you can do more. Avoid adding thousands of controls at once—use lazy loading or pagination. Batch
multiple UI updates with page.batch_update() to reduce WebSocket roundtrips. Cache expensive
computations (e.g., API responses) in memory or client storage. Profile your app with browser dev tools to spot slow
renders. For lists, use ListView with item_extent for fixed-height items to enable
virtualization. Remember: premature optimization is evil, but thoughtful design prevents bottlenecks before they
happen.
import flet as ft
def main(page: ft.Page):
large_list = ft.ListView(height=400)
# Efficient batch update
with page.batch_update():
for i in range(500):
large_list.controls.append(
ft.ListTile(title=ft.Text(f"Item {i}"), dense=True)
)
page.add(
ft.Text("Optimized Large List", size=20, weight="bold"),
large_list
)
ft.app(target=main)
Don’t repeat yourself—wrap common UI patterns into reusable components. Flet’s UserControl class lets
you create self-contained widgets with their own state, layout, and event handlers. For example, a
StarRating control, a ProductCard, or a LoginForm. These can be instantiated
multiple times, customized via constructor arguments, and even published as PyPI packages. This promotes modularity,
simplifies testing, and accelerates development. Think of UserControl as your building blocks for
complex UIs—compose them like LEGO to create rich experiences.
import flet as ft
class ProductCard(ft.UserControl):
def __init__(self, name, price, image_url, on_add):
super().__init__()
self.name = name
self.price = price
self.image_url = image_url
self.on_add = on_add
def build(self):
return ft.Card(
content=ft.Container(
ft.Column([
ft.Image(src=self.image_url, height=150, fit=ft.ImageFit.COVER),
ft.Text(self.name, weight="bold"),
ft.Text(f"${self.price}", color="#e53e3e", weight="bold"),
ft.ElevatedButton("Add to Cart", on_click=self.on_add)
], spacing=10, alignment="center"),
padding=15
)
)
def main(page: ft.Page):
def add_item(e):
page.snack_bar = ft.SnackBar(ft.Text("Added to cart!"), open=True)
page.update()
page.add(
ft.Text("Reusable Product Cards", size=22, weight="bold"),
ft.Row([
ProductCard("Laptop", 999, "https://picsum.photos/200/150?1", add_item),
ProductCard("Phone", 699, "https://picsum.photos/200/150?2", add_item)
], scroll="auto")
)
ft.app(target=main)
If your app serves users worldwide, localization is non-negotiable. Flet supports internationalization through
simple string substitution. Store translations in dictionaries or JSON files (e.g., en.json,
es.json), and load them based on user preference or system settings. Update all UI text dynamically
when the language changes. For large apps, integrate with babel for extraction and compilation of
translation files. Remember to localize dates, numbers, and currencies too—Python’s locale module
helps. This small effort dramatically expands your app’s reach and inclusivity.
import flet as ft
TRANSLATIONS = {
"en": {"greet": "Hello", "submit": "Submit", "lang": "Language"},
"es": {"greet": "Hola", "submit": "Enviar", "lang": "Idioma"},
"fr": {"greet": "Bonjour", "submit": "Soumettre", "lang": "Langue"}
}
def main(page: ft.Page):
current_lang = "es"
t = TRANSLATIONS[current_lang]
def change_lang(e):
nonlocal current_lang, t
current_lang = "fr" if current_lang == "es" else "es"
t = TRANSLATIONS[current_lang]
greet.value = t["greet"]
submit_btn.text = t["submit"]
lang_label.value = f'{t["lang"]}: {current_lang.upper()}'
page.update()
greet = ft.Text(t["greet"], size=24)
lang_label = ft.Text(f'{t["lang"]}: {current_lang.upper()}', italic=True)
submit_btn = ft.ElevatedButton(t["submit"])
page.add(
ft.Text("Internationalization Demo", size=20, weight="bold"),
greet,
lang_label,
submit_btn,
ft.ElevatedButton("Switch Language", on_click=change_lang)
)
ft.app(target=main)
Great apps work for all users, including those with disabilities. Flet generates semantic HTML and ARIA attributes
automatically, but you can enhance accessibility further. Use tooltip on icons for screen readers,
ensure sufficient color contrast, and support keyboard navigation (Flet controls are focusable by default). Test
your app with accessibility tools like Lighthouse or VoiceOver. Small touches—like labeling form fields with
label instead of placeholder text—make a huge difference. Inclusive design isn’t just ethical; it’s
good business, expanding your potential user base.
import flet as ft
def main(page: ft.Page):
# Accessible form
email_field = ft.TextField(
label="Email address", # Visible label for screen readers
hint_text="you@example.com",
width=300
)
submit_btn = ft.ElevatedButton(
"Subscribe",
tooltip="Click to subscribe to our newsletter", # ARIA label
on_click=lambda e: page.add(ft.Text("Subscribed!"))
)
page.add(
ft.Text("Accessible Form Example", size=20, weight="bold"),
email_field,
submit_btn
)
ft.app(target=main)
Bugs are inevitable, but Flet gives you great tools to squash them. Use print() for quick debugging,
or integrate Python’s logging module for structured output. In web mode, inspect WebSocket traffic in
browser dev tools (Network > WS) to see UI updates. Wrap event handlers in try/except to catch and display errors
gracefully. For complex state issues, log your variables before/after mutations. Remember: Flet’s reactive model
means UI bugs are usually state bugs—focus there first. And when stuck, simplify: create a minimal reproducible
example to isolate the problem.
import flet as ft
import logging
logging.basicConfig(level=logging.DEBUG)
def main(page: ft.Page):
def risky_calculation(e):
try:
result = 100 / 0 # Intentional error
except ZeroDivisionError as ex:
logging.error(f"Math error in calculation: {ex}")
page.snack_bar = ft.SnackBar(
ft.Text("Oops! Division by zero."),
bgcolor="#e53e3e",
open=True
)
page.update()
page.add(
ft.Text("Debugging Demo", size=20, weight="bold"),
ft.ElevatedButton("Trigger Error", on_click=risky_calculation)
)
ft.app(target=main)
For large-scale applications, structure is everything. Adopt architectural patterns like MVVM
(Model-View-ViewModel) to separate concerns: the Model handles data/business logic,
the ViewModel prepares data for the UI, and the View
(Flet controls) displays it. This makes your code testable (mock the Model), maintainable (change UI without
touching logic), and team-friendly (clear responsibilities). In Flet, the main() function is your View
layer; keep it thin by delegating to ViewModel classes. This investment pays off as your app evolves from prototype
to production.
import flet as ft
# Model: Business logic
class User:
def __init__(self, name, email):
self.name = name
self.email = email
# ViewModel: UI logic
class UserProfileViewModel:
def __init__(self, user: User):
self.user = user
def get_display_name(self):
return f"Welcome, {self.user.name}!"
def is_valid_email(self):
return "@" in self.user.email
# View: Flet UI
def main(page: ft.Page):
user = User("Alex Morgan", "alex@example.com")
vm = UserProfileViewModel(user)
status = "✅ Valid" if vm.is_valid_email() else "❌ Invalid"
page.add(
ft.Text(vm.get_display_name(), size=22),
ft.Text(f"Email: {user.email} {status}")
)
ft.app(target=main)
Flet is more than a framework—it’s a growing community of developers sharing knowledge, components, and tools.
Contribute to the official GitHub repo, ask questions on Discord, or publish your own UserControl
packages on PyPI. The ecosystem already includes plugins for rich text, maps, charts, and authentication. By
leveraging community work, you avoid reinventing the wheel and accelerate development. And when you build something
useful, share it back—this virtuous cycle is what makes open source thrive. Remember: every expert was once a
beginner, so be kind and generous in your interactions.
# Example: Using a community package
# pip install flet-nested-router
import flet as ft
from flet_nested_router import NestedRouter
def main(page: ft.Page):
router = NestedRouter(page)
# Define nested routes like /admin/users, /admin/settings
# Simplifies complex navigation
page.add(ft.Text("Community plugins extend Flet's power!"))
ft.app(target=main)
Flet is evolving rapidly, with a clear vision: to make Python the ultimate language for building any application. Upcoming features include offline support (service workers), advanced theming (per-user dark/light mode), better mobile gestures (swipe, pinch), and tighter Flutter integration (custom native views). The team is also working on performance enhancements, developer tooling (debugger, profiler), and enterprise features (SSO, audit logs). By staying close to Flutter’s innovations while preserving Python’s simplicity, Flet aims to blur the lines between desktop, web, and mobile development. The future is cross-platform, real-time, and Pythonic—and Flet is leading the charge.
# Stay updated with the official roadmap:
# https://github.com/flet-dev/flet/blob/main/ROADMAP.md
# Key upcoming features:
# - Offline mode (PWA support)
# - Enhanced mobile gestures
# - Custom native views (via Flutter plugins)
# - Built-in authentication
Flet isn’t the only option, so when should you use it? Choose Flet when you need: a single codebase for web/desktop/mobile, real-time collaboration, or rapid prototyping in Python. Prefer Streamlit/Dash for data-heavy apps with minimal interactivity (they rerun the whole script on every interaction, which Flet avoids). Use Tkinter for simple, lightweight desktop tools where modern UI isn’t critical. Pick Electron if you’re already deep in JavaScript and need maximum web compatibility. Flet shines when you want modern UIs, complex interactions, and Python simplicity—all without context switching. It’s the sweet spot for 80% of internal tools, MVPs, and productivity apps.
# Decision guide:
# - Need real-time multi-user? → Flet
# - Building a data dashboard? → Streamlit (simple) or Flet (complex)
# - Simple desktop utility? → Tkinter
# - Full web app with JS ecosystem? → React/Vue
# - Python-only, cross-platform, interactive? → Flet ✅
Let’s synthesize everything into a real-world application: a task manager with projects, due dates, and persistence. This app features routing (home/settings), forms (add task), lists (task view), dialogs (delete confirmation), client storage (save tasks), and responsive layout. Notice how state management, event handling, and UI composition work together. This isn’t just a demo—it’s a foundation you can extend into a full productivity tool. The beauty of Flet is that complex apps remain readable and maintainable because the code mirrors the UI structure.
import flet as ft
import json
from datetime import datetime
class TaskManager:
def __init__(self, page: ft.Page):
self.page = page
self.tasks = self.load_tasks()
def load_tasks(self):
data = self.page.client_storage.get("tasks")
return json.loads(data) if data else []
def save_tasks(self):
self.page.client_storage.set("tasks", json.dumps(self.tasks))
def add_task(self, title, project="General"):
self.tasks.append({
"id": len(self.tasks) + 1,
"title": title,
"project": project,
"done": False,
"created": datetime.now().isoformat()
})
self.save_tasks()
def toggle_task(self, task_id):
for task in self.tasks:
if task["id"] == task_id:
task["done"] = not task["done"]
break
self.save_tasks()
def delete_task(self, task_id):
self.tasks = [t for t in self.tasks if t["id"] != task_id]
self.save_tasks()
def main(page: ft.Page):
tm = TaskManager(page)
task_input = ft.TextField(hint_text="New task...", expand=True)
task_list = ft.ListView(spacing=10, padding=10)
def refresh_tasks():
task_list.controls.clear()
for task in tm.tasks:
task_list.controls.append(
ft.Checkbox(
label=task["title"],
value=task["done"],
on_change=lambda e, tid=task["id"]: (tm.toggle_task(tid), refresh_tasks())
)
)
page.update()
def add_task(e):
if task_input.value:
tm.add_task(task_input.value)
task_input.value = ""
refresh_tasks()
task_input.on_submit = add_task
add_btn = ft.IconButton(ft.icons.ADD, on_click=add_task)
page.add(
ft.AppBar(title=ft.Text("Task Manager"), bgcolor="#2d3436"),
ft.Row([task_input, add_btn]),
task_list
)
refresh_tasks()
ft.app(target=main)
Flet represents a renaissance in Python GUI development—a return to simplicity without sacrificing power. It proves that you don’t need to abandon Python to build modern, cross-platform applications. By eliminating the frontend/backend divide, Flet lets you focus on what matters: solving problems and delighting users. Whether you’re a data scientist automating reports, a teacher building educational tools, or a startup founder shipping an MVP, Flet removes the friction that traditionally slowed Python developers down. The journey from idea to shipped app has never been shorter. So go ahead: write that app you’ve been dreaming of. With Flet, your Python skills are all you need.
Final Thought: The best way to master Flet is to build something real—today. Start small, iterate often, and let your creativity flow. The community is here to support you, and the possibilities are endless.
# Your next great app starts with one line:
# import flet as ft