5 Python GUI Libraries That Make UI Development Fun (Beyond Tkinter)
Tired of Tkinter’s dated look, this article evaluates five modern Python GUI toolkits—PyQt6, Dear PyGui, Flet, Textual, and CustomTkinter—by outlining their core features, code snippets, ideal use‑cases, and a decision matrix to help you choose the most suitable framework for desktop, web, or terminal interfaces.
Why move away from Tkinter
Tkinter’s layout management and styling are cumbersome, making simple GUIs feel outdated. Modern Python GUI libraries provide faster development, richer widgets, and better theming.
1. PyQt6 – Professional‑grade desktop UI
Core advantages
True cross‑platform support
Extensive widget set and powerful layout system
CSS‑style theming via Qt Style Sheets
Robust signal‑slot mechanism for event handling
Quick‑start example
import sys
from PyQt6.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout
app = QApplication(sys.argv)
window = QWidget()
window.setWindowTitle("PyQt6 Quick Start")
window.setGeometry(100, 100, 300, 200)
layout = QVBoxLayout()
label = QLabel("Welcome to PyQt6!")
button = QPushButton("Click Me")
def on_button_click():
label.setText("Button clicked!")
button.clicked.connect(on_button_click)
layout.addWidget(label)
layout.addWidget(button)
window.setLayout(layout)
window.show()
sys.exit(app.exec())Usage insights
Learning curve: moderate – initial effort to grasp signal‑slot patterns, then high productivity.
Best suited for professional desktop applications and complex enterprise tools.
2. Dear PyGui – GPU‑accelerated lightweight GUI
Distinctive features
Game‑engine based rendering architecture provides GPU acceleration for all widgets.
Rich built‑in data‑visualization components (charts, progress bars, etc.).
Very high performance, ideal for real‑time monitoring panels.
Data‑monitoring panel example
import dearpygui.dearpygui as dpg
import random, time
dpg.create_context()
def update_data():
current_time = time.strftime("%H:%M:%S")
cpu = random.randint(20, 90)
mem = random.randint(30, 85)
dpg.set_value("time_text", f"Time: {current_time}")
dpg.set_value("cpu_bar", cpu/100)
dpg.set_value("cpu_value", f"CPU: {cpu}%")
dpg.set_value("mem_bar", mem/100)
dpg.set_value("mem_value", f"Mem: {mem}%")
with dpg.window(label="System Monitor", width=400, height=300):
dpg.add_text("System Status", color=(0,0,255))
dpg.add_separator()
dpg.add_text("", tag="time_text")
dpg.add_text("CPU Usage")
dpg.add_progress_bar(tag="cpu_bar", width=300)
dpg.add_text("", tag="cpu_value")
dpg.add_text("Memory Usage")
dpg.add_progress_bar(tag="mem_bar", width=300)
dpg.add_text("", tag="mem_value")
dpg.add_button(label="Start", width=100)
dpg.add_button(label="Stop", width=100)
dpg.set_frame_callback(10, update_data)
dpg.create_viewport(title='System Monitor', width=420, height=350)
dpg.setup_dearpygui()
dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()Recommended scenarios
Internal monitoring tools that require real‑time updates.
Adding a UI layer to automation scripts.
Rapid prototyping of data‑heavy interfaces.
3. Flet – Build web apps with pure Python
Key benefits
Write once, deploy to web, desktop, and mobile (Flutter backend).
Material Design components are available out of the box.
Responsive layout system requires no manual CSS or JavaScript.
File‑manager example
import flet as ft
import os
from pathlib import Path
def main(page: ft.Page):
page.title = "Python File Manager"
page.theme_mode = ft.ThemeMode.DARK
page.padding = 20
current_path = ft.Text(value=f"Current dir: {os.getcwd()}", size=16)
file_list = ft.Column()
def list_files():
file_list.controls.clear()
if Path.cwd().parent != Path.cwd():
file_list.controls.append(
ft.ListTile(
leading=ft.Icon(ft.icons.FOLDER_OPEN),
title=ft.Text(".. (Parent)"),
on_click=lambda e: change_directory(Path.cwd().parent)
)
)
for item in sorted(Path.cwd().iterdir()):
icon = ft.icons.FOLDER if item.is_dir() else ft.icons.INSERT_DRIVE_FILE
color = ft.colors.BLUE_200 if item.is_dir() else ft.colors.WHITE
file_list.controls.append(
ft.ListTile(
leading=ft.Icon(icon, color=color),
title=ft.Text(item.name),
subtitle=ft.Text(f"Size: {item.stat().st_size:,} bytes" if item.is_file() else "Directory"),
on_click=lambda e, p=item: open_item(p)
)
)
page.update()
def change_directory(new_path):
os.chdir(new_path)
current_path.value = f"Current dir: {os.getcwd()}"
list_files()
def open_item(item_path):
if item_path.is_dir():
change_directory(item_path)
else:
page.show_snack_bar(ft.SnackBar(ft.Text(f"Opening file: {item_path.name}")))
list_files()
page.add(ft.Text("Python File Manager", size=24, weight=ft.FontWeight.BOLD))
page.add(current_path)
page.add(ft.Divider())
page.add(ft.Container(content=file_list, height=400, border=ft.border.all(1, ft.colors.GREY_800), border_radius=10, padding=10))
page.add(ft.Row([
ft.ElevatedButton("Refresh", icon=ft.icons.REFRESH, on_click=lambda e: list_files()),
ft.ElevatedButton("About", icon=ft.icons.INFO, on_click=lambda e: page.show_dialog(ft.AlertDialog(title=ft.Text("About"), content=ft.Text("File manager built with Flet"))))
]))
ft.app(target=main)Best fit
Full‑stack Python developers who want to avoid front‑end technologies.
Quick prototyping of web interfaces (minutes to a usable UI).
Internal tools that need to be shared across a team.
4. Textual – Modern TUI for the terminal
Why use Textual
Preserves the full terminal workflow while providing GUI‑like widgets.
SSH‑friendly; remote servers can display the interface without X forwarding.
Very low resource consumption compared to graphical toolkits.
Automation task manager example
from textual.app import App, ComposeResult
from textual.widgets import Header, Footer, Button, Static, Label
from textual.containers import Container, Vertical
import asyncio
class TaskManager(App):
"""Task manager application"""
CSS = """
Screen { background: $surface; }
.task-container { height: 1fr; border: solid $primary; padding: 1; }
.success { color: $success; }
.error { color: $error; }
.running { color: $warning; }
"""
def compose(self) -> ComposeResult:
yield Header()
with Container(id="main-container"):
with Vertical(id="left-panel"):
yield Label("Automation Task Manager", classes="title")
yield Button("Run Data Clean", id="clean-data", variant="primary")
yield Button("Generate Report", id="generate-report", variant="success")
yield Button("Backup DB", id="backup-db", variant="warning")
with Vertical(id="right-panel", classes="task-container"):
yield Label("Task Status", classes="subtitle")
yield Static("", id="status-display")
yield Footer()
def on_button_pressed(self, event: Button.Pressed) -> None:
button_id = event.button.id
status_display = self.query_one("#status-display")
if button_id == "clean-data":
status_display.update("[bold]Cleaning data...[/]")
self.run_task(self.simulate_task, "Data Clean", 3)
elif button_id == "generate-report":
status_display.update("[bold]Generating report...[/]")
self.run_task(self.simulate_task, "Report", 2)
elif button_id == "backup-db":
status_display.update("[bold]Backing up DB...[/]")
self.run_task(self.simulate_task, "DB Backup", 4)
async def simulate_task(self, task_name: str, duration: int) -> None:
status_display = self.query_one("#status-display")
for i in range(duration):
status_display.update(f"[bold]{task_name} running... ({i+1}/{duration}s)[/]")
await asyncio.sleep(1)
status_display.update(f"[bold green]✓ {task_name} completed![/]")
if __name__ == "__main__":
app = TaskManager()
app.run()Ideal use cases
Server‑management tools accessed via SSH.
Enhancing existing CLI utilities with interactive UI.
Developing auxiliary tools such as linters or test runners.
5. CustomTkinter – Modernising classic Tkinter
What it provides
Material‑Design styling and built‑in dark theme.
Improved layout handling through CTkTabview and CTk widgets.
Minimal code changes required for existing Tkinter projects.
Modern settings panel example
import customtkinter as ctk
import tkinter as tk
from tkinter import messagebox
class SettingsApp:
def __init__(self):
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")
self.app = ctk.CTk()
self.app.title("Modern Settings Panel")
self.app.geometry("500x400")
self.tabview = ctk.CTkTabview(self.app)
self.tabview.pack(fill="both", expand=True, padx=20, pady=20)
self.tabview.add("General")
self.tabview.add("Appearance")
self.tabview.add("About")
self.setup_general_tab()
self.setup_appearance_tab()
self.setup_about_tab()
def setup_general_tab(self):
tab = self.tabview.tab("General")
self.auto_start = ctk.CTkSwitch(tab, text="Auto‑Start on Boot", command=self.on_auto_start_change)
self.auto_start.pack(pady=10, anchor="w", padx=20)
self.auto_update = ctk.CTkSwitch(tab, text="Check for Updates")
self.auto_update.pack(pady=10, anchor="w", padx=20)
ctk.CTkLabel(tab, text="Log Level:").pack(pady=(20,5), anchor="w", padx=20)
self.log_level = ctk.CTkComboBox(tab, values=["DEBUG","INFO","WARNING","ERROR"])
self.log_level.pack(pady=5, anchor="w", padx=20, fill="x")
def setup_appearance_tab(self):
tab = self.tabview.tab("Appearance")
ctk.CTkLabel(tab, text="Theme Mode:").pack(pady=10, anchor="w", padx=20)
self.theme_mode = ctk.CTkSegmentedButton(tab, values=["Light","Dark","System"], command=self.on_theme_change)
self.theme_mode.set("Dark")
self.theme_mode.pack(pady=5, anchor="w", padx=20, fill="x")
ctk.CTkLabel(tab, text="Color Theme:").pack(pady=10, anchor="w", padx=20)
self.color_theme = ctk.CTkComboBox(tab, values=["Blue","Green","Purple","Orange"])
self.color_theme.pack(pady=5, anchor="w", padx=20, fill="x")
def setup_about_tab(self):
tab = self.tabview.tab("About")
info_text = """
Modern Settings Panel Example
Version: 1.0.0
Author: 云朵君
Built with CustomTkinter for a modern UI experience
"""
ctk.CTkLabel(tab, text=info_text, justify="left").pack(pady=20, padx=20)
btn_frame = ctk.CTkFrame(tab)
btn_frame.pack(pady=20)
ctk.CTkButton(btn_frame, text="Check Updates", width=100).pack(side="left", padx=10)
ctk.CTkButton(btn_frame, text="Visit Website", width=100).pack(side="left", padx=10)
def on_auto_start_change(self):
status = "On" if self.auto_start.get() else "Off"
print(f"Auto‑Start: {status}")
def on_theme_change(self, value):
mapping = {"Light": "light", "Dark": "dark", "System": "system"}
ctk.set_appearance_mode(mapping[value])
print(f"Theme switched to: {value}")
def run(self):
self.app.mainloop()
if __name__ == "__main__":
app = SettingsApp()
app.run()When to use
Modernising existing Tkinter projects with minimal code changes.
Quickly adding a polished UI to legacy tools.
Decision guide
Flet – choose for web or multi‑platform apps.
Dear PyGui – choose for high‑performance monitoring dashboards.
PyQt6 – choose for professional desktop software.
Textual – choose to enhance existing command‑line utilities.
CustomTkinter – choose to modernise an existing Tkinter project.
Migrating from Tkinter
Gradual migration – replace widgets step‑by‑step with CustomTkinter equivalents.
Unified styling – use the built‑in theme system to keep a consistent look.
Feature boost – gain a modern appearance while preserving the original code structure.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Data STUDIO
Click to receive the "Python Study Handbook"; reply "benefit" in the chat to get it. Data STUDIO focuses on original data science articles, centered on Python, covering machine learning, data analysis, visualization, MySQL and other practical knowledge and project case studies.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
