Theme Basics
Flet supports three theme modes:import flet as ft
def main(page: ft.Page):
# Set theme mode
page.theme_mode = ft.ThemeMode.LIGHT # Light mode
# page.theme_mode = ft.ThemeMode.DARK # Dark mode
# page.theme_mode = ft.ThemeMode.SYSTEM # Follow system (default)
page.add(ft.Text("Hello, themed world!"))
ft.run(main)
Theme Modes
- Light Mode
- Dark Mode
- System
import flet as ft
def main(page: ft.Page):
page.theme_mode = ft.ThemeMode.LIGHT
page.add(
ft.Container(
content=ft.Text("Light theme"),
bgcolor=ft.Colors.SURFACE,
padding=20,
)
)
import flet as ft
def main(page: ft.Page):
page.theme_mode = ft.ThemeMode.DARK
page.add(
ft.Container(
content=ft.Text("Dark theme"),
bgcolor=ft.Colors.SURFACE,
padding=20,
)
)
import flet as ft
def main(page: ft.Page):
# Follows system theme (default)
page.theme_mode = ft.ThemeMode.SYSTEM
# Detect current brightness
brightness = page.platform_brightness
page.add(ft.Text(f"Current brightness: {brightness}"))
# Listen for theme changes
def on_brightness_change(e):
page.add(ft.Text(f"Theme changed to: {e.brightness}"))
page.on_platform_brightness_change = on_brightness_change
Color Schemes
Seed Colors
Generate a complete theme from a single seed color:import flet as ft
def main(page: ft.Page):
# Generate theme from seed color
page.theme = ft.Theme(
color_scheme_seed=ft.Colors.GREEN,
)
page.add(
ft.FilledButton("Primary Color Button"),
ft.OutlinedButton("Secondary Button"),
ft.Text("Theme generated from green seed color"),
)
ft.run(main)
Custom Color Scheme
Define specific colors for your theme:import flet as ft
def main(page: ft.Page):
# Custom light theme
page.theme = ft.Theme(
color_scheme=ft.ColorScheme(
primary=ft.Colors.BLUE,
on_primary=ft.Colors.WHITE,
secondary=ft.Colors.AMBER,
on_secondary=ft.Colors.BLACK,
surface=ft.Colors.GREY_100,
on_surface=ft.Colors.BLACK,
error=ft.Colors.RED,
on_error=ft.Colors.WHITE,
)
)
# Custom dark theme
page.dark_theme = ft.Theme(
color_scheme=ft.ColorScheme(
primary=ft.Colors.BLUE_300,
on_primary=ft.Colors.BLACK,
secondary=ft.Colors.AMBER_300,
on_secondary=ft.Colors.BLACK,
surface=ft.Colors.GREY_900,
on_surface=ft.Colors.WHITE,
error=ft.Colors.RED_300,
on_error=ft.Colors.BLACK,
)
)
page.add(
ft.FilledButton("Primary Button"),
ft.Container(
content=ft.Text("Surface Container"),
bgcolor=ft.Colors.SURFACE,
padding=20,
),
)
ft.run(main)
Color Scheme Properties
Material Design 3 provides extensive color roles:import flet as ft
def main(page: ft.Page):
page.theme = ft.Theme(
color_scheme=ft.ColorScheme(
# Primary colors
primary=ft.Colors.BLUE,
on_primary=ft.Colors.WHITE,
primary_container=ft.Colors.BLUE_100,
on_primary_container=ft.Colors.BLUE_900,
# Secondary colors
secondary=ft.Colors.GREEN,
on_secondary=ft.Colors.WHITE,
secondary_container=ft.Colors.GREEN_100,
on_secondary_container=ft.Colors.GREEN_900,
# Tertiary colors
tertiary=ft.Colors.ORANGE,
on_tertiary=ft.Colors.WHITE,
tertiary_container=ft.Colors.ORANGE_100,
on_tertiary_container=ft.Colors.ORANGE_900,
# Error colors
error=ft.Colors.RED,
on_error=ft.Colors.WHITE,
error_container=ft.Colors.RED_100,
on_error_container=ft.Colors.RED_900,
# Surface colors
surface=ft.Colors.GREY_50,
on_surface=ft.Colors.BLACK,
surface_variant=ft.Colors.GREY_100,
on_surface_variant=ft.Colors.GREY_700,
# Utility colors
outline=ft.Colors.GREY_400,
outline_variant=ft.Colors.GREY_200,
shadow=ft.Colors.BLACK,
scrim=ft.Colors.BLACK,
inverse_surface=ft.Colors.GREY_800,
on_inverse_surface=ft.Colors.WHITE,
inverse_primary=ft.Colors.BLUE_200,
)
)
ft.run(main)
Using Theme Colors
Reference theme colors in your controls:import flet as ft
def main(page: ft.Page):
page.theme = ft.Theme(color_scheme_seed=ft.Colors.PURPLE)
page.add(
# Use theme colors
ft.Container(
content=ft.Text("Primary Surface"),
bgcolor=ft.Colors.PRIMARY_CONTAINER,
padding=20,
),
ft.Container(
content=ft.Text("Secondary Surface"),
bgcolor=ft.Colors.SECONDARY_CONTAINER,
padding=20,
),
ft.Container(
content=ft.Text("Surface"),
bgcolor=ft.Colors.SURFACE,
padding=20,
),
)
ft.run(main)
Nested Themes
Apply different themes to specific parts of your app:import flet as ft
def main(page: ft.Page):
# Page theme (yellow)
page.theme = ft.Theme(
color_scheme_seed=ft.Colors.YELLOW,
)
page.add(
# Inherits page theme
ft.Container(
content=ft.Button("Page theme button"),
bgcolor=ft.Colors.SURFACE_TINT,
padding=20,
width=300,
),
# Override with pink theme
ft.Container(
theme=ft.Theme(
color_scheme=ft.ColorScheme(primary=ft.Colors.PINK)
),
content=ft.Button("Pink theme button"),
bgcolor=ft.Colors.SURFACE_TINT,
padding=20,
width=300,
),
# Unique dark theme (always dark)
ft.Container(
theme=ft.Theme(color_scheme_seed=ft.Colors.INDIGO),
theme_mode=ft.ThemeMode.DARK,
content=ft.Button("Dark theme button"),
bgcolor=ft.Colors.SURFACE_TINT,
padding=20,
width=300,
),
)
ft.run(main)
Text Themes
Customize typography across your app:import flet as ft
def main(page: ft.Page):
page.theme = ft.Theme(
text_theme=ft.TextTheme(
# Display styles (largest)
display_large=ft.TextStyle(size=57, weight=ft.FontWeight.NORMAL),
display_medium=ft.TextStyle(size=45, weight=ft.FontWeight.NORMAL),
display_small=ft.TextStyle(size=36, weight=ft.FontWeight.NORMAL),
# Headline styles
headline_large=ft.TextStyle(size=32, weight=ft.FontWeight.NORMAL),
headline_medium=ft.TextStyle(size=28, weight=ft.FontWeight.NORMAL),
headline_small=ft.TextStyle(size=24, weight=ft.FontWeight.NORMAL),
# Title styles
title_large=ft.TextStyle(size=22, weight=ft.FontWeight.W500),
title_medium=ft.TextStyle(size=16, weight=ft.FontWeight.W500),
title_small=ft.TextStyle(size=14, weight=ft.FontWeight.W500),
# Body styles
body_large=ft.TextStyle(size=16, weight=ft.FontWeight.NORMAL),
body_medium=ft.TextStyle(size=14, weight=ft.FontWeight.NORMAL),
body_small=ft.TextStyle(size=12, weight=ft.FontWeight.NORMAL),
# Label styles
label_large=ft.TextStyle(size=14, weight=ft.FontWeight.W500),
label_medium=ft.TextStyle(size=12, weight=ft.FontWeight.W500),
label_small=ft.TextStyle(size=11, weight=ft.FontWeight.W500),
)
)
page.add(
ft.Text("Display Large", theme_style=ft.TextThemeStyle.DISPLAY_LARGE),
ft.Text("Headline Medium", theme_style=ft.TextThemeStyle.HEADLINE_MEDIUM),
ft.Text("Body Large", theme_style=ft.TextThemeStyle.BODY_LARGE),
ft.Text("Label Small", theme_style=ft.TextThemeStyle.LABEL_SMALL),
)
ft.run(main)
Custom Fonts
Load and use custom fonts:import flet as ft
def main(page: ft.Page):
# Register custom fonts
page.fonts = {
"Roboto": "https://github.com/google/fonts/raw/main/apache/roboto/static/Roboto-Regular.ttf",
"Pacifico": "https://github.com/google/fonts/raw/main/ofl/pacifico/Pacifico-Regular.ttf",
"CustomFont": "/fonts/custom-font.ttf", # Local asset
}
page.add(
ft.Text("Default font"),
ft.Text("Roboto font", font_family="Roboto"),
ft.Text("Pacifico font", font_family="Pacifico", size=30),
)
ft.run(main)
Component Themes
Customize specific component styles:import flet as ft
def main(page: ft.Page):
page.theme = ft.Theme(
# Button theme
elevated_button_theme=ft.ButtonThemeData(
style=ft.ButtonStyle(
shape=ft.RoundedRectangleBorder(radius=10),
padding=20,
bgcolor={ft.ControlState.DEFAULT: ft.Colors.BLUE},
)
),
# TextField theme
input_decoration_theme=ft.InputDecorationTheme(
border=ft.OutlineInputBorder(
border_radius=8,
border_side=ft.BorderSide(width=2, color=ft.Colors.BLUE),
),
focused_border=ft.OutlineInputBorder(
border_radius=8,
border_side=ft.BorderSide(width=2, color=ft.Colors.BLUE_700),
),
),
# AppBar theme
app_bar_theme=ft.AppBarTheme(
bgcolor=ft.Colors.BLUE,
color=ft.Colors.WHITE,
),
)
page.add(
ft.ElevatedButton("Themed Button"),
ft.TextField(label="Themed Input"),
)
ft.run(main)
Page Transitions
Customize page transition animations:import flet as ft
def main(page: ft.Page):
page.theme = ft.Theme(
page_transitions=ft.PageTransitionsTheme(
android=ft.PageTransitionTheme.FADE_UPWARDS,
ios=ft.PageTransitionTheme.CUPERTINO,
linux=ft.PageTransitionTheme.ZOOM,
macos=ft.PageTransitionTheme.ZOOM,
windows=ft.PageTransitionTheme.ZOOM,
)
)
ft.run(main)
Material vs Cupertino
Use adaptive controls that automatically switch between Material and Cupertino:import flet as ft
def main(page: ft.Page):
# Adaptive controls automatically use:
# - Material design on Android/Windows/Linux/Web
# - Cupertino design on iOS/macOS
page.add(
ft.AdaptiveButton(
text="Adaptive Button",
# Material on Android, Cupertino on iOS
),
ft.AdaptiveTextField(
label="Adaptive Input",
),
)
# Force Cupertino style
page.add(
ft.CupertinoButton(
content=ft.Text("Cupertino Button"),
),
ft.CupertinoTextField(
placeholder="Cupertino Input",
),
)
ft.run(main)
Dynamic Theme Switching
Allow users to toggle theme at runtime:import flet as ft
def main(page: ft.Page):
def toggle_theme(e):
# Toggle between light and dark
page.theme_mode = (
ft.ThemeMode.DARK
if page.theme_mode == ft.ThemeMode.LIGHT
else ft.ThemeMode.LIGHT
)
page.update()
def change_seed_color(e):
# Change theme seed color
colors = [ft.Colors.BLUE, ft.Colors.GREEN, ft.Colors.RED, ft.Colors.PURPLE]
import random
page.theme = ft.Theme(color_scheme_seed=random.choice(colors))
page.update()
page.add(
ft.Switch(
label="Dark mode",
value=page.theme_mode == ft.ThemeMode.DARK,
on_change=toggle_theme,
),
ft.ElevatedButton("Random Color", on_click=change_seed_color),
)
ft.run(main)
Visual Density
Adjust spacing and sizing:import flet as ft
def main(page: ft.Page):
page.theme = ft.Theme(
visual_density=ft.VisualDensity.COMPACT, # More compact
# visual_density=ft.VisualDensity.COMFORTABLE, # Default
# visual_density=ft.VisualDensity.STANDARD, # More spacious
)
page.add(
ft.FilledButton("Button 1"),
ft.FilledButton("Button 2"),
ft.FilledButton("Button 3"),
)
ft.run(main)
Best Practices
Common pitfalls to avoid:
- Hardcoding colors instead of using theme colors
- Not providing both light and dark theme variants
- Using inconsistent color schemes across the app
- Ignoring platform conventions (Material vs Cupertino)
Use Theme Colors
import flet as ft
def main(page: ft.Page):
# Good: Use theme colors
page.add(
ft.Container(
content=ft.Text("Themed"),
bgcolor=ft.Colors.PRIMARY, # Adapts to theme
)
)
# Avoid: Hardcoded colors
page.add(
ft.Container(
content=ft.Text("Hardcoded"),
bgcolor="#0000FF", # Won't adapt to theme changes
)
)
Provide Dark Mode
import flet as ft
def main(page: ft.Page):
# Always provide both light and dark themes
page.theme = ft.Theme(color_scheme_seed=ft.Colors.BLUE)
page.dark_theme = ft.Theme(color_scheme_seed=ft.Colors.BLUE)
page.theme_mode = ft.ThemeMode.SYSTEM # Respect user preference
Test Both Modes
import flet as ft
def main(page: ft.Page):
# Add a theme switcher for testing
def toggle_theme(e):
page.theme_mode = (
ft.ThemeMode.DARK
if page.theme_mode == ft.ThemeMode.LIGHT
else ft.ThemeMode.LIGHT
)
page.update()
page.add(
ft.IconButton(
icon=ft.Icons.BRIGHTNESS_6,
on_click=toggle_theme,
)
)
Next Steps
Architecture
Understand Flet’s client-server architecture
Page and Controls
Learn about the control tree and Page object