Declarative dialogs#
ft.use_dialog() lets a component show and update dialogs declaratively.
Instead of imperatively calling page.show_dialog() and later
remembering to close or remove the dialog, you render a DialogControl
from component state:
- pass a dialog instance to show it;
- pass
Noneto hide it.
This keeps dialog logic in the same state flow as the rest of a declarative app:
- state decides whether the dialog is visible;
- dialog content updates when state changes;
- there is no
page.update()call in the component.
Basic pattern#
Call ft.use_dialog() on every render. When the dialog should be open,
return a dialog control; otherwise return None.
import flet as ft
@ft.component
def App():
show, set_show = ft.use_state(False)
ft.use_dialog(
ft.AlertDialog(
modal=True,
title=ft.Text("Delete report.pdf?"),
content=ft.Text("This cannot be undone."),
actions=[
ft.TextButton("Delete", on_click=lambda: set_show(False)),
ft.TextButton("Cancel", on_click=lambda: set_show(False)),
],
on_dismiss=lambda: set_show(False),
)
if show
else None
)
return ft.Column(
controls=[
ft.TextButton("Open dialog", on_click=lambda: set_show(True)),
]
)
ft.run(lambda page: page.render(App))
The important part is that show is the source of truth. The dialog is not opened by
mutating the page tree directly; it appears because the component renders it.
Updating dialog content from state#
Because the dialog is declarative, its content can react to state changes while it is open. This is useful for confirmations, form validation, and async workflows:
import asyncio
import flet as ft
@ft.component
def App():
show, set_show = ft.use_state(False)
deleting, set_deleting = ft.use_state(False)
async def handle_delete():
set_deleting(True)
await asyncio.sleep(2)
set_deleting(False)
set_show(False)
ft.use_dialog(
ft.AlertDialog(
modal=True,
title=ft.Text("Delete report.pdf?"),
content=ft.Text(
"Deleting, please wait..." if deleting else "This cannot be undone."
),
actions=[
ft.Button(
"Deleting..." if deleting else "Delete",
disabled=deleting,
on_click=handle_delete,
),
ft.TextButton(
"Cancel",
disabled=deleting,
on_click=lambda: set_show(False),
),
],
on_dismiss=lambda: set_show(False),
)
if show
else None
)
return ft.TextButton("Delete file", on_click=lambda: set_show(True))
ft.run(lambda page: page.render(App))
This pattern works well with asyncio and other async APIs in Flet apps. For more
background, see Async apps.
Chaining dialogs#
You can call ft.use_dialog() more than once in the same component. That
makes follow-up flows straightforward, for example:
- confirmation dialog;
- async action;
- success dialog after the first dialog fully closes.
import flet as ft
@ft.component
def App():
show_confirm, set_show_confirm = ft.use_state(False)
show_success, set_show_success = ft.use_state(False)
should_chain = ft.use_ref(False)
def confirm_delete():
should_chain.current = True
set_show_confirm(False)
def on_confirm_dismiss():
if should_chain.current:
should_chain.current = False
set_show_success(True)
ft.use_dialog(
ft.AlertDialog(
title=ft.Text("Delete file?"),
actions=[
ft.TextButton("Delete", on_click=confirm_delete),
ft.TextButton("Cancel", on_click=lambda: set_show_confirm(False)),
],
on_dismiss=on_confirm_dismiss,
)
if show_confirm
else None
)
ft.use_dialog(
ft.AlertDialog(
title=ft.Text("Done"),
content=ft.Text("The file was deleted."),
actions=[
ft.TextButton("OK", on_click=lambda: set_show_success(False)),
],
)
if show_success
else None
)
return ft.TextButton("Open", on_click=lambda: set_show_confirm(True))
ft.run(lambda page: page.render(App))
ft.use_ref() is helpful here because the value survives re-renders without
causing another render by itself.
on_dismiss timing#
DialogControl.on_dismiss fires after the dialog close
animation completes, not immediately when open changes to False. This makes it safe to
start follow-up UI after the dialog has actually finished closing.
Use on_dismiss for logic that should happen after the dialog is fully gone, such as:
- opening the next dialog in a chain;
- resetting temporary dialog-local state;
- starting a follow-up animation or toast.
When to use page.show_dialog() instead#
ft.use_dialog() is a better fit inside @ft.component
functions and other declarative flows.
page.show_dialog() is still a good option when:
- the app is written imperatively;
- dialog lifecycle is handled outside the component tree;
- you need to trigger a dialog from existing page-level event code and do not want to convert that part of the app to declarative style yet.
In practice, the two APIs serve different styles:
- use declarative dialogs when UI should follow component state;
- use imperative dialogs when UI is managed by direct page mutation.