agentsclimarketplace

Flet best practices

Skill mykaro/ai-skills/skills/flet-best-practices

Idiomatic guide for Flet 1.0 applications. Covers declarative UI, hooks, observable state, services, async execution model, and breaking API changes from v0.x. Use this skill to write modern Flet code, migrate imperative apps to reactive patterns, apply breaking changes correctly, and avoid common pitfalls.From its SKILL.md

Install
npx -y skills add mykaro/ai-skills --skill flet-best-practices

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

12.2 KB, ~3.0k tokens by cl100k_base, as published. Nobody here has run it

1. Main ideas (TL;DR)

  • Flet 1.0 — complete architecture overhaul (not an incremental release).
  • Alongside the existing imperative approach, a declarative/reactive approach has appeared.
  • UI is now described as a pure function of state: UI = f(state).
  • The framework itself decides which parts of the tree to update — update() is no longer needed.
  • Inspiration: React, SwiftUI, Jetpack Compose, SolidJS.
  • Architectural changes: dataclasses instead of manual conversions, binary protocol (MessagePack), InheritedWidget + Provider instead of Redux.

2. Imperative vs Declarative

Imperative (old approach)

# You say HOW to build the UI step by step
left_column.visible = False
right_column.visible = True
right_column.controls.append(ft.Text("Complete!"))
page.update()

Problem: state, logic, and UI live in one place. As the application grows, the number of synchronization points grows exponentially.

Declarative (new approach)

# You describe WHAT to show for a given state
@ft.component
def App():
    count, set_count = ft.use_state(0)
    return ft.Row(controls=[
        ft.Text(value=f"{count}"),
        ft.Button("Add", on_click=lambda: set_count(count + 1)),
    ])

ft.run(lambda page: page.render(App))

Advantage: UI is always an exact reflection of the state. The framework itself updates only what has changed.


3. Components (components)

Component — a function with the @ft.component decorator that takes parameters and returns Flet controls.

@ft.component
def Greeting(name):
    return ft.Text(f"Hello, {name}!")

Control vs Component

ControlComponent
What it isUI element (Text, Button, Row)Function that builds and returns controls
RenderedDirectlyNo — through controls in its return
Exampleft.Text("Hi")@ft.component def Card(title): ...

Rules for writing components

  • Capitalized names: def Counter(...), not def counter(...).
  • The @ft.component decorator is mandatory for a functional component.
  • The function always returns a single control or a list of controls.
  • Do not call page.update() inside a component.
  • Components can be nested inside each other to build complex UI.

4. Hooks (hooks)

Hooks — lightweight functions that allow a component to store state, react to lifecycle events, or get shared context — without classes.

⚠️ Local variables are recreated on every render — their values disappear. Hooks survive the render.

Built-in Flet hooks

HookPurpose
use_state(initial)Local state that is preserved between renders
use_effect(fn, deps)Side effects when dependencies change
use_context(key)Access to shared data / services
use_memo(fn, deps)Memoization of computations

Example of use_state

@ft.component
def Counter():
    count, set_count = ft.use_state(0)
    return ft.Row(controls=[
        ft.Text(value=f"{count}"),
        ft.Button("Add", on_click=lambda: set_count(count + 1)),
    ])

OOP analogy for understanding

# Imaginary: the use_state(0) hook is a class field with state
class Counter(Component):
    count: state(0)
    def build(self):
        return Row(...)

5. Observable (observable objects)

Observable — a reactive data holder: when the value changes — the UI updates automatically.

Two ways to make a class observable:

# Option 1: inheritance
@dataclass
class AppState(ft.Observable):
    count: int

# Option 2: decorator
@dataclass
@ft.observable
class AppState:
    count: int

Full example with Observable

import asyncio
from dataclasses import dataclass
import flet as ft

@dataclass
@ft.observable
class AppState:
    counter: float

    async def start_counter(self):
        self.counter = 0
        for _ in range(10):
            self.counter += 0.1
            await asyncio.sleep(0.5)

@ft.component
def App():
    state, _ = ft.use_state(AppState(counter=0))
    return [
        ft.ProgressBar(state.counter),
        ft.Button("Run!", on_click=state.start_counter),
    ]

ft.run(lambda page: page.render(App))

Observable vs React: Observable allows mutable state, React requires full replacement. Optimization: several quick changes to Observable properties are merged into a single UI update.


6. Services (services)

Service — a persistent, non-visual component that "survives" page updates and navigation.

Adding a service

# The service must be added to page.services
file_picker = ft.FilePicker()
page.services.append(file_picker)

Converted to services (Breaking Change!)

  • FilePicker
  • Audio (ext: flet-audio)
  • AudioRecorder (ext: flet-audio-recorder)
  • HapticFeedback
  • Flashlight, Geolocator, PermissionHandler, SemanticsService, ShakeDetector

FilePicker — new async API

# Old approach (v0): via result event handlers
# New approach (v1): async methods that immediately return the result
files = await file_picker.pick_files_async(allow_multiple=True)
file_name = await file_picker.save_file_async()
dir_name = await file_picker.get_directory_path_async()

7. Auto-update

In Flet 1.0, Control.update() is called automatically after the event handler finishes.

Most applications no longer need an explicit update().

For long-running operations — use yield:

async def button_click():
    progress.value = "Something started"
    yield                        # ← force UI update right now
    await asyncio.sleep(3)
    progress.value = "Something finished"

8. Bootstrap: how to run a declarative app

import flet as ft

# Minimal option
def App():
    return ft.Text("Hello, world!")

ft.run(lambda page: page.render(App))

# Explicit option
@ft.component
def App():
    return ft.Text("Hello, Flet!")

def main(page: ft.Page):
    page.render(App)

ft.run(main)

⚠️ The declarative approach must go top-down — the entire UI tree is declarative. Just like async code remains async everywhere.


9. Utilities: Ref, context, page

Access to page from anywhere

# Instead of passing page into every function:
print(ft.context.page.web)

Ref — reference to a specific control

from dataclasses import dataclass, field

@dataclass
class State:
    txt_name: ft.Ref[ft.TextField] = field(default_factory=lambda: ft.Ref())

@ft.component
def App(state):
    return ft.TextField(ref=state.txt_name)

Controlled inputs (recommended approach for forms)

@dataclass
@ft.observable
class Form:
    name: str = ""

    def set_name(self, value):
        self.name = value

    async def submit(self, e: ft.Event[ft.Button]):
        e.page.show_dialog(
            ft.AlertDialog(title="Hello", content=ft.Text(f"Hello, {self.name}!"))
        )

    async def reset(self):
        self.name = ""

@ft.component
def App():
    form, _ = ft.use_state(Form())
    return [
        ft.TextField(
            label="Your name",
            value=form.name,
            on_change=lambda e: form.set_name(e.control.value),
        ),
        ft.Row([
            ft.FilledButton("Submit", on_click=form.submit),
            ft.FilledTonalButton("Reset", on_click=form.reset),
        ]),
    ]

10. Breaking Changes (Flet 1.0)

Async model

  • Single-threaded async UI — like JavaScript or Flutter.
  • time.sleep() freezes the UI → use await asyncio.sleep().
  • CPU-bound tasks → await asyncio.to_thread(...).
  • All get/set methods of controls — are now async.

API changes

What changedWas (v0)Became (v1)
App launchft.app(target=main)ft.run(main)
Dialogs (open)page.open(dialog)page.show_dialog(dialog)
Dialogs (close)page.pop_dialog()
Drawerspage.drawer = ...NavigationDrawer(position=...) + page.show_dialog()
Clipboardpage.set_clipboard(v)page.clipboard.set_async(v)
Clipboardpage.get_clipboard()page.clipboard.get_async()
Client storagepage.client_storagepage.shared_preferences
ButtonsButton(text="...")Button(content=...) — no text!
Scroll intervalon_scroll_intervalscroll_interval
Chartsbuilt-inseparate package flet-charts
Control IDe.target — stringe.target — integer
FilePickercontrols listpage.services

before_main hook

def config(page: ft.Page):
    page.on_resize = lambda e: print("Page resized!")

def main(page: ft.Page):
    page.add(ft.Text("Hello!"))

ft.run(main, before_main=config)

Event handlers — parameter e is now optional

button_1.on_click = lambda: print("Clicked!")       # without e — OK
button_2.on_click = lambda e: print("Clicked!", e)  # with e — also OK

11. New architecture (under the hood)

ComponentWasBecame
Python controlsmanual conversionsdataclasses with auto-constructor
UI diffingbasicnew algorithm for imperative+declarative
Dart state mgmtReduxInheritedWidget + Provider
ProtocolJSONMessagePack (binary, less traffic)
Binary database64raw binary
Extensions APIfunction createControlclass FletExtension with createWidget + createService

12. Web: WASM and Offline

WebAssembly

  • WASM is enabled by default (where supported by the browser).
  • Renderer: SKWASM (for WASM) + CanvasKit (for Dart2JS fallback).

Offline / no-CDN mode

# Via code
ft.run(main, no_cdn=True)

# Via environment variable
# FLET_WEB_NO_CDN=1

# Via CLI
flet build web --no-cdn

Bundles: CanvasKit, SkWASM, Pyodide, fonts.


13. Installing Flet 1.0

# pip
pip install --pre 'flet[all]>=0.70.0.dev0'

# uv
uv add 'flet[all]>=0.70.0.dev0' --prerelease=allow

pyproject.toml for flet build

dependencies = [
    "flet >=0.70.0.dev0",
    "flet-audio >=0.2.0.dev0",
    "flet-video >=0.2.0.dev0",
]

Extensions v1 have version 0.2.x+, extensions v0 — 0.1.x.


14. Best Practices

✅ Correct

# 1. Use @ft.component for every reusable UI piece
@ft.component
def UserCard(name, avatar_url):
    return ft.Row([ft.Image(src=avatar_url), ft.Text(name)])

# 2. State — in Observable or use_state, not in global variables
@dataclass
@ft.observable
class AppState:
    items: list[str]

# 3. Async event handlers for IO operations
async def on_save(e):
    await db.save_async(data)

# 4. yield to display intermediate state in long-running operations
async def on_process(e):
    progress.visible = True
    yield
    await do_heavy_work()
    progress.visible = False

# 5. Services — in page.services
page.services.append(ft.FilePicker())

❌ Incorrect

# Do not use time.sleep() — freezes the UI
time.sleep(3)  # ❌
await asyncio.sleep(3)  # ✅

# Do not call page.update() in a declarative app
page.update()  # ❌ in declarative mode

# Do not add FilePicker to controls — it is now a service
page.overlay.append(ft.FilePicker())  # ❌
page.services.append(ft.FilePicker())  # ✅

# Do not use page.open() for dialogs
page.open(dialog)  # ❌
page.show_dialog(dialog)  # ✅

# Do not use text= in buttons
ft.ElevatedButton(text="Click")  # ❌
ft.ElevatedButton(content=ft.Text("Click"))  # ✅

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,834. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.