74.3 tkinter.ttk: Themed Widgets
Alright, let’s talk about ttk. You’ve probably been using basic tkinter widgets so far. They get the job done, but let’s be honest, they look like they time-traveled here from 1995 and they’re not happy about it. Enter ttk — the “Themed Tkinter” module. This isn’t just a fresh coat of paint; it’s a fundamental shift in how Tkinter handles widgets.
Think of vanilla tkinter widgets as drawing directly on the canvas. Every button is a unique, hand-drawn rectangle. ttk widgets, on the other hand, are like actors. The widget itself is just the logic — the behavior, the methods, the properties. The look is provided by a separate “theme” (the costume and set design). This separation of style and logic is why ttk is so powerful. Your code can instantly adopt the native look and feel of Windows, macOS, or Linux (via the ‘clam’, ‘alt’, or ‘classic’ themes if native isn’t available) just by changing the theme. It’s a huge win for professionalism.
First, you need to import it. Always. You’ll be using a mix of tkinter and ttk from now on.
import tkinter as tk
from tkinter import ttk
The Core Conceptual Shift: Options and Style
This is the biggest mental leap. Vanilla Tkinter uses options like bg, fg, and font. ttk largely throws those out the window. Instead, it uses a style system. Trying to use button.config(bg='red') on a ttk.Button will do precisely nothing, which is a fantastic way to waste an afternoon debugging. Trust me.
Instead, you define a Style object and configure the look of widget classes. A ttk.Button is not just a Button; it’s a TButton (see the ‘T’ prefix?).
window = tk.Tk()
# Create a style object
style = ttk.Style()
# Configure the background of ALL TButtons in the application
style.configure('TButton', background='lightblue')
my_button = ttk.Button(window, text="I'm a fancy ttk button!")
my_button.pack()
window.mainloop()
Why Widgets Feel Different (and Better)
ttk widgets aren’t just prettier; they’re often more functional and consistent with modern UX. A ttk.Combobox is a dropdown and a text entry field, which is what users expect. A ttk.Progressbar actually exists. The ttk.Notebook widget gives you tabbed interfaces without having to hack it together yourself. These are proper, modern GUI components.
Here’s a quick example of a ttk.Combobox and a ttk.Progressbar:
import tkinter as tk
from tkinter import ttk
import time
def on_go():
# Simulate some work
for i in range(1, 101):
progress_var.set(i) # Update the progressbar value
window.update_idletasks() # Force the GUI to update
time.sleep(0.02) # Simulate time-consuming work
combo_value.set("Done!")
window = tk.Tk()
# Combobox with a list of choices
choices = ['Option 1', 'Option 2', 'Option 3']
combo_value = tk.StringVar()
combo = ttk.Combobox(window, textvariable=combo_value, values=choices)
combo.pack(pady=10)
# Progressbar linked to an IntVar
progress_var = tk.IntVar()
progress = ttk.Progressbar(window, variable=progress_var, maximum=100)
progress.pack(pady=10, fill='x', padx=20)
# Button to start the action
go_button = ttk.Button(window, text="Go", command=on_go)
go_button.pack(pady=10)
window.mainloop()
Taming the Style System: A Deeper Dive
The style system is both ttk’s superpower and its most annoying feature. You can create custom styles that inherit from base styles. The syntax is 'ParentStyle.YourStyleName'. Let’s make a prominent, danger-style button.
window = tk.Tk()
style = ttk.Style()
# Create a new style called 'Danger.TButton' that inherits from 'TButton'
style.configure('Danger.TButton', foreground='white', background='#dc3545')
# Use our new custom style
danger_btn = ttk.Button(window, text="Do Not Click", style='Danger.TButton')
danger_btn.pack()
window.mainloop()
Why inherit from TButton? Because you want to keep all the core behavior and layout of a button. You’re just changing the colors. You could also create a style for a specific widget instance, but using a named, reusable style is almost always the better practice.
The Rough Edges and Pitfalls
ttk is not perfect. The documentation for the style system is… let’s call it “aspirational.” Figuring out the correct style name for every part of every widget (the Treeview header, the Notebook tab focus, etc.) is an exercise in guesswork and using the style.layout() and style.element_options() methods to interrogate the theme itself.
Another common gotcha is the state of a widget. While vanilla Tkinter might use 'disabled', ttk uses a tuple of state flags, like ('disabled',) or ('selected', 'focus'). To disable a ttk widget, you use widget.state(['disabled']). To enable it, you use widget.state(['!disabled']). It’s more powerful but less intuitive.
Best Practice: Embrace the separation. Don’t fight the style system. Define all your styles in one place, ideally at the start of your application. This makes your UI consistent and makes it trivial to change the entire theme later. And for the love of all that is holy, use ttk widgets over the vanilla ones unless you have a very specific reason not to. Your users will thank you.