tkinter — Cursor rule: what it does, how to install
Guides Python desktop GUI development in Cursor by enforcing ttkbootstrap themes, class-based views, and clean grid layouts.
.cursor/rules/tkinter.mdcWhat tkinter does
The rule targets **/*.py files to keep Python desktop interfaces maintainable. It stops the assistant from creating legacy tk widgets, switching default UI code to tkinter.ttk or ttkbootstrap for dark and flat themes.
Architecture is constrained to class-based views inheriting from a common base view and managed by an application controller. Layouts rely strictly on unmixed geometry managers: grid handles complex views, while pack stays reserved for simple stacks. Chaining widget creation with layout calls is prohibited because it drops the widget reference to None.
For dynamic data binding, the rule enforces Tkinter variables like StringVar and IntVar. The event loop must remain unblocked. Freezing the UI with time.sleep() is forbidden in favor of root.after(), threads, or asyncio.
What it helps with
- Build modern Python desktop interfaces with ttkbootstrap themes
- Structure multi-screen GUI code using class-based views and a controller
- Prevent TclError crashes caused by mixing pack and grid in one frame
- Bind dynamic inputs using StringVar and IntVar objects
- Keep the GUI event loop responsive without time.sleep blocking
Example prompts for the agent
- Create a login window using ttkbootstrap with the superhero theme
- Refactor this monolithic Tkinter script into BaseView classes and an AppController
- Build a settings form using grid layout and IntVar data bindings
How to install tkinter
Manually: put the files in place
.cursor/rules/tkinter.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/tkinter.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/tkinter.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/tkinter.mdManually: put the files in place
.clinerules/tkinter.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about tkinter
How to install the tkinter rule in Cursor?
Save the rule file to .cursor/rules/tkinter.mdc in your project root.
What does the tkinter rule do?
It enforces Tkinter coding guidelines, requiring ttk or ttkbootstrap widgets, class-based views, consistent grid layouts, and Tkinter variable bindings.
Why does the rule prohibit mixing pack and grid?
Calling both pack() and grid() inside the same parent container raises a TclError and breaks window layout calculations.
Source text
tkinter.mdc · CC0-1.0
---
description: Definitive guidelines for building modern, maintainable, and performant Tkinter applications using best practices, ttk, and ttkbootstrap.
globs: **/*.py
---
# tkinter Best Practices
Tkinter is Python's standard GUI toolkit. To build modern, maintainable, and responsive applications in 2025, adhere to these opinionated best practices.
## 1. Modern Theming: `ttk` and `ttkbootstrap`
Always use `tkinter.ttk` widgets for a native look and feel. For a truly modern aesthetic, integrate `ttkbootstrap`.
* **Default to `ttk`**: `ttk` widgets automatically adopt the OS theme.
* **Embrace `ttkbootstrap`**: For a flat, dark-mode-ready, and consistent cross-platform design, `ttkbootstrap` is the go-to. It's a drop-in replacement for `ttk` with enhanced styling.
❌ BAD: Using classic `tk` widgets
```python
import tkinter as tk
root = tk.Tk()
tk.Button(root, text="Old Button").pack()
```
✅ GOOD: Using `ttk` widgets
```python
import tkinter as tk
from tkinter import ttk # Always import ttk
root = tk.Tk()
ttk.Button(root, text="Themed Button").pack()
```
✅ BEST: Using `ttkbootstrap` for modern themes
```python
import ttkbootstrap as ttk # Import ttkbootstrap as ttk
from ttkbootstrap.constants import * # For constants like PRIMARY, INFO
root = ttk.Window(themename="superhero") # Apply a modern theme
ttk.Button(root, text="Bootstrap Button", bootstyle="primary").pack(pady=10)
```
## 2. Code Organization: Class-Based Views
Structure your application with clear separation of concerns. Use classes for each logical view or component to encapsulate widgets and their logic.
* **Main Application Class**: Manages the root window and switches between views.
* **View Classes**: Each class represents a distinct part of the UI (e.g., `LoginView`, `DashboardView`).
* **Thin Bootstrap**: A minimal `main.py` to launch the app.
❌ BAD: Monolithic script
```python
import tkinter as tk
# ... hundreds of lines of mixed UI and logic ...
def create_ui():
root = tk.Tk()
# ...
root.mainloop()
create_ui()
```
✅ GOOD: Class-based structure
```python
import ttkbootstrap as ttk
from typing import Type
class BaseView(ttk.Frame):
def __init__(self, master: ttk.Window, controller: 'AppController') -> None:
super().__init__(master)
self.controller = controller
class MainView(BaseView):
def __init__(self, master: ttk.Window, controller: 'AppController') -> None:
super().__init__(master, controller)
ttk.Label(self, text="Welcome to the App!").pack(pady=20)
ttk.Button(self, text="Go to Settings", command=self.controller.show_settings).pack()
class SettingsView(BaseView):
def __init__(self, master: ttk.Window, controller: 'AppController') -> None:
super().__init__(master, controller)
ttk.Label(self, text="Settings Page").pack(pady=20)
ttk.Button(self, text="Back to Main", command=self.controller.show_main).pack()
class AppController:
def __init__(self, root: ttk.Window) -> None:
self.root = root
self.current_view: BaseView | None = None
self._views: dict[str, Type[BaseView]] = {
"main": MainView,
"settings": SettingsView
}
self.show_main()
def _switch_view(self, view_name: str) -> None:
if self.current_view:
self.current_view.destroy()
ViewClass = self._views[view_name]
self.current_view = ViewClass(self.root, self)
self.current_view.pack(fill=ttk.BOTH, expand=True)
def show_main(self) -> None:
self._switch_view("main")
def show_settings(self) -> None:
self._switch_view("settings")
if __name__ == "__main__":
root = ttk.Window(themename="flatly")
root.title("My Modern Tkinter App")
root.geometry("400x300")
app = AppController(root)
root.mainloop()
```
## 3. Geometry Managers: `grid` is King, `pack` for Simplicity
Choose one geometry manager per parent container and stick to it. `grid` offers the most control and is generally preferred for complex layouts. Use `pack` only for very simple, linear arrangements.
* **Never mix `pack()` and `grid()` in the same parent widget.** This is a common source of layout bugs.
* **Separate widget creation from layout calls.** Chaining `widget.pack()` returns `None`, making the widget unusable later.
❌ BAD: Mixing `pack` and `grid` in the same frame
```python
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root)
frame.pack()
tk.Label(frame, text="Label 1").pack()
tk.Button(frame, text="Button 1").grid(row=0, column=1) # ❌ Will cause TclError
```
❌ BAD: Chaining geometry manager calls
```python
import tkinter as tk
root = tk.Tk()
my_button = tk.Button(root, text="Click Me").pack() # my_button is now None
# my_button.config(text="New Text") # ❌ AttributeError: 'NoneType' object has no attribute 'config'
```
✅ GOOD: Using `grid` consistently
```python
import ttkbootstrap as ttk
root = ttk.Window()
root.title("Grid Layout")
# Configure grid to expand with window
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
main_frame = ttk.Frame(root)
main_frame.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
# Configure frame's grid
main_frame.columnconfigure(1, weight=1) # Make column 1 expandable
ttk.Label(main_frame, text="Username:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
username_entry = ttk.Entry(main_frame)
username_entry.grid(row=0, column=1, sticky="ew", padx=5, pady=5)
ttk.Label(main_frame, text="Password:").grid(row=1, column=0, sticky="w", padx=5, pady=5)
password_entry = ttk.Entry(main_frame, show="*")
password_entry.grid(row=1, column=1, sticky="ew", padx=5, pady=5)
ttk.Button(main_frame, text="Login").grid(row=2, column=0, columnspan=2, pady=10)
```
## 4. Data Binding: Use Tkinter Variables
For dynamic UI elements (labels, entries, checkboxes), use `StringVar`, `IntVar`, `BooleanVar`, or `DoubleVar`. These variables notify linked widgets of changes automatically.
❌ BAD: Updating labels with regular Python variables
```python
import tkinter as tk
count = 0
def increment():
global count
count += 1
label.config(text=f"Count: {count}") # Manual update
root = tk.Tk()
label = tk.Label(root, text=f"Count: {count}")
label.pack()
tk.Button(root, text="Increment", command=increment).pack()
```
✅ GOOD: Using `tk.IntVar`
```python
import tkinter as tk
root = tk.Tk()
count_var = tk.IntVar(value=0) # Initialize with a Tkinter variable
def increment():
count_var.set(count_var.get() + 1) # Update the Tkinter variable
label = tk.Label(root, textvariable=count_var) # Link label to variable
label.pack()
tk.Button(root, text="Increment", command=increment).pack()
```
## 5. Responsiveness: Avoid Blocking the Event Loop
Never use `time.sleep()` in a GUI application's main thread. It will freeze the entire UI. For delayed actions, use `root.after()`. For long-running tasks, use threading or `asyncio`.
❌ BAD: Freezing the UI with `time.sleep()`
```python
import tkinter as tk
import time
root = tk.Tk()
status_label = tk.Label(root, text="Ready")
status_label.pack()
def long_task():
status_label.config(text="Working...")
root.update_idletasks() # Force update, but still blocks
time.sleep(3) # ❌ GUI FREEZES for 3 seconds
status_label.config(text="Done!")
tk.Button(root, text="Start Task", command=long_task).pack()
```
✅ GOOD: Non-blocking delays with `root.after()`
```python
import tkinter as tk
root = tk.Tk()
status_label = tk.Label(root, text="Ready")
status_label.pack()
def finish_task():
status_label.config(text="Done!")
def start_task():
status_label.config(text="Working...")
root.after(3000, finish_task) # Schedule finish_task after 3000ms (3 seconds)
tk.Button(root, text="Start Task", command=start_task).pack()
```
## 6. Image Handling: Keep References
Tkinter does not store references to `PhotoImage` objects internally. If your Python variable holding the `PhotoImage` goes out of scope, the image will disappear from the widget.
❌ BAD: Image disappears due to garbage collection
```python
import tkinter as tk
from PIL import Image, ImageTk # Requires Pillow: pip install Pillow
root = tk.Tk()
def create_image_label():
img = Image.open("icon.png") # Assume icon.png exists
photo = ImageTk.PhotoImage(img)
label = tk.Label(root, image=photo) # photo might be garbage collected
label.pack()
create_image_label()
```
✅ GOOD: Explicitly keep a reference to the image
```python
import tkinter as tk
from PIL import Image, ImageTk
root = tk.Tk()
img = Image.open("icon.png") # Assume icon.png exists
photo = ImageTk.PhotoImage(img)
label = tk.Label(root, image=photo)
label.image = photo # ✅ Keep a reference directly on the widget
label.pack()
```
## 7. Essential Boilerplate: `root.mainloop()`
Always call `root.mainloop()` as the very last statement in your main application script. Without it, your GUI window will flash and immediately close.
❌ BAD: Window flashes and disappears
```python
import tkinter as tk
root = tk.Tk()
root.title("My App")
tk.Label(root, text="Hello!").pack()
# Forgot root.mainloop()
```
✅ GOOD: Proper application lifecycle
```python
import tkinter as tk
root = tk.Tk()
root.title("My App")
tk.Label(root, text="Hello!").pack()
root.mainloop() # ✅ This is non-negotiable
```
## 8. Robust Callbacks: Handle Exceptions
Wrap potentially failing operations in callbacks with `try...except` blocks. Tkinter often swallows exceptions in event handlers, making debugging difficult. Use `messagebox` to provide user feedback.
❌ BAD: Silent crashes in callbacks
```python
import tkinter as tk
def risky_action():
# This will raise FileNotFoundError if 'nonexistent.txt' doesn't exist
with open("nonexistent.txt", "r") as f:
content = f.read()
print(content) # This line might never be reached
root = tk.Tk()
tk.Button(root, text="Do Risky Action", command=risky_action).pack()
```
✅ GOOD: Graceful error handling
```python
import tkinter as tk
from tkinter import messagebox
def risky_action_safe():
try:
with open("nonexistent.txt", "r") as f:
content = f.read()
messagebox.showinfo("Success", "File read successfully!")
except FileNotFoundError:
messagebox.showerror("Error", "The file 'nonexistent.txt' was not found.")
except Exception as e:
messagebox.showerror("Error", f"An unexpected error occurred: {e}")
root = tk.Tk()
tk.Button(root, text="Do Risky Action Safely", command=risky_action_safe).pack()
```
## 9. Type Hints
Use type hints consistently for improved readability, maintainability, and static analysis. This is standard modern Python practice.
❌ BAD: Untyped functions and methods
```python
class MyWidget(tk.Frame):
def __init__(self, master, data):
super().__init__(master)
self.data = data
def update_label(self, new_text):
# ...
```
✅ GOOD: Typed functions and methods
```python
import tkinter as tk
from typing import Any
class MyWidget(tk.Frame):
def __init__(self, master: tk.Tk | tk.Frame, data: dict[str, Any]) -> None:
super().__init__(master)
self.data = data
self.label = tk.Label(self, text=self.data.get("name", ""))
self.label.pack()
def update_label(self, new_text: str) -> None:
self.label.config(text=new_text)
```
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc