Previous | Tutorial index | | Next: none
ttk.Style class for customising widget appearances.Combobox, Notebook, Progressbar, Treeview, etc.) to build modern interfaces.If you have built a GUI using classic Tkinter widgets, you may have noticed they look... dated. The classic widgets have a distinct 1990s appearance—grey, chunky 3D bevels, and a fixed look that does not adapt well to modern operating systems.
Enter Ttk (Themed Tkinter). Introduced in Tk 8.5 and available in Python's tkinter.ttk module, Ttk provides a set of widgets that leverage the platform's native theming engine. On Windows 10/11, Ttk widgets look like native Windows controls; on macOS, they adopt Aqua styling; on Linux (with GTK), they blend in with the desktop environment.
But Ttk is not just about good looks—it introduces a new styling API, state management, and new widgets that are not available in classic Tk. This tutorial will guide you through everything you need to transition from classic to themed Tkinter.
The standard practice is to import both classic Tkinter and Ttk side‑by‑side:
import tkinter as tk # Classic widgets
from tkinter import ttk # Themed widgets
You can also do import tkinter.ttk as ttk, but the above is more common.
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.title("Ttk Example")
# Classic label
tk.Label(root, text="Classic Label", bg="yellow").pack(pady=5)
# Themed label
ttk.Label(root, text="Themed Label").pack(pady=5)
root.mainloop()
Notice the difference: the Ttk label has a clean, flat appearance that matches your operating system, while the classic label has a grey background (unless you specify bg).
A theme is a collection of styles that define the appearance of all Ttk widgets. Themes are platform‑specific and can be switched at runtime.
print(ttk.Style().theme_names())
Common themes:
'vista', 'xpnative' (Windows 7/10/11 native).'aqua'.'clam', 'alt', 'default', 'classic' (depending on GTK).'clam' and 'alt' are available everywhere.style = ttk.Style()
style.theme_use('clam') # Switch to the 'clam' theme
You can change the entire look of your application with one line:
def toggle_theme():
current = style.theme_use()
if current == 'clam':
style.theme_use('vista')
else:
style.theme_use('clam')
This is a huge advantage over classic Tk, where you would have to manually reconfigure every widget's colours.
ttk.StyleClassic Tk allows you to set colours and fonts directly:
tk.Button(root, text="Click", bg="red", fg="white", font=("Arial", 12))
Ttk does NOT allow this. You cannot pass bg, fg, font, or relief to a Ttk widget's constructor. If you try, it will be silently ignored (or raise an error in some cases). Instead, you must use the ttk.Style class.
ttk.Style Class – Your Styling ToolkitThe Style class manages the appearance of all Ttk widgets. Here are its core methods:
| Method | Description |
|---|---|
configure(style_name, **options) |
Sets style options for a given widget class (e.g., "TButton"). |
map(style_name, **options) |
Defines dynamic style changes based on widget states (e.g., hover, pressed). |
layout(style_name, layout_spec) |
Defines the internal layout of a widget (advanced). |
theme_names() |
Returns a tuple of available themes. |
theme_use(theme_name) |
Sets the current theme. |
Each Ttk widget has a style name that you use to configure it. The naming convention is "T" + widget class name (with the first letter capitalised):
| Widget | Style Name |
|---|---|
ttk.Button |
"TButton" |
ttk.Label |
"TLabel" |
ttk.Entry |
"TEntry" |
ttk.Frame |
"TFrame" |
ttk.LabelFrame |
"TLabelframe" |
ttk.Checkbutton |
"TCheckbutton" |
ttk.Radiobutton |
"TRadiobutton" |
ttk.Combobox |
"TCombobox" |
ttk.Progressbar |
"TProgressbar" |
ttk.Notebook |
"TNotebook" |
ttk.Treeview |
"Treeview" (note: no leading "T") |
style = ttk.Style()
style.configure("TButton", font=("Helvetica", 12), foreground="blue", background="yellow")
Important: The options you can set depend on the theme and the widget. Common options include:
font – a tuple (family, size, weight).foreground / background – text/background colour (but many themes ignore background for buttons).padding – a tuple (left, top, right, bottom).relief – 'flat', 'raised', 'sunken', etc. (but often ignored by themes).anchor – text alignment.You can create custom style names that inherit from a base style. This is useful for having different styles for different buttons.
# Create a custom style called "Success.TButton" that inherits from "TButton"
style.configure("Success.TButton", foreground="green", font=("Arial", 10, "bold"))
style.configure("Danger.TButton", foreground="red")
btn1 = ttk.Button(root, text="Save", style="Success.TButton")
btn2 = ttk.Button(root, text="Delete", style="Danger.TButton")
.map()The .map() method allows you to change styles based on the widget's state (e.g., hover, pressed, disabled). This is how modern UIs provide visual feedback.
Syntax:
style.map(style_name, **state_specs)
state_specs is a dictionary where keys are options (like foreground, background) and values are lists of (state, value) tuples.
Example: Button hover effect
style.map("TButton",
foreground=[('pressed', 'red'), ('active', 'blue')],
background=[('active', 'lightgrey')],
relief=[('pressed', 'sunken'), ('!pressed', 'raised')])
'active' – mouse is hovering over the widget.'pressed' – mouse button is down.'disabled' – widget is disabled.'!pressed' – NOT pressed (the ! means negation).The ttk.Notebook widget (tabs) has its own style components:
"TNotebook" – the outer frame."TNotebook.Tab" – the individual tabs.style.configure("TNotebook.Tab", font=("Arial", 10, "bold"), padding=[10, 5])
style.map("TNotebook.Tab",
background=[('selected', 'lightblue'), ('active', 'lightyellow')])
.configure() vs .map().configure() – sets the base appearance (always applied)..map() – sets appearance that changes with states (interactive feedback).Ttk provides 18 widgets in total:
| Ttk Widget | Classic Equivalent | Notes |
|---|---|---|
ttk.Button |
tk.Button |
Themed, no bg/fg support. |
ttk.Label |
tk.Label |
Themed, no direct colour options. |
ttk.Entry |
tk.Entry |
Themed. |
ttk.Frame |
tk.Frame |
Themed. |
ttk.LabelFrame |
tk.LabelFrame |
Themed. |
ttk.Checkbutton |
tk.Checkbutton |
Themed. |
ttk.Radiobutton |
tk.Radiobutton |
Themed. |
ttk.Scale |
tk.Scale |
Themed. |
ttk.Scrollbar |
tk.Scrollbar |
Themed. |
ttk.Listbox |
No direct equivalent – see Treeview. |
Ttk does not have a direct Listbox; use Treeview for lists. |
ttk.Menu |
No direct equivalent | Use classic tk.Menu with Ttk. |
ttk.PanedWindow |
No direct equivalent | Use classic tk.PanedWindow or the one in Ttk (some themes support it). |
These are the real power‑ups of Ttk:
Combobox – Dropdown with EditingA Combobox combines an Entry with a dropdown list. The user can either type a value or select from the list.
combo = ttk.Combobox(root, values=["Apple", "Banana", "Cherry"], state="readonly")
combo.pack()
combo.set("Banana") # Set default
selected = combo.get()
state="readonly" – prevents typing, only dropdown selection.state="normal" – allows typing (auto‑complete is not built‑in).Notebook – Tabbed InterfaceA Notebook creates a tabbed container where each tab holds a different frame.
notebook = ttk.Notebook(root)
tab1 = ttk.Frame(notebook)
tab2 = ttk.Frame(notebook)
notebook.add(tab1, text="Tab 1")
notebook.add(tab2, text="Tab 2")
notebook.pack(fill=tk.BOTH, expand=True)
# Add content to tabs
ttk.Label(tab1, text="Content of Tab 1").pack()
ttk.Label(tab2, text="Content of Tab 2").pack()
# Select a tab programmatically
notebook.select(tab1) # or notebook.select(0) for index
Use .add(child, text, image, compound) to add tabs.
Bind to <<NotebookTabChanged>> to detect tab switches:
def on_tab_change(event):
selected_tab = notebook.index(notebook.select())
print(f"Switched to tab {selected_tab}")
notebook.bind("<<NotebookTabChanged>>", on_tab_change)
Progressbar – Visual Progress IndicatorA Progressbar shows the progress of a long‑running operation.
progress = ttk.Progressbar(root, orient=tk.HORIZONTAL, length=200, mode='determinate')
progress.pack()
# Update progress
progress['value'] = 50 # 0 to 100
# Indeterminate mode (for unknown duration)
progress.config(mode='indeterminate')
progress.start(50) # Starts animation (step every 50ms)
progress.stop() # Stops animation
mode='determinate' – shows a specific percentage.mode='indeterminate' – shows an animated bar (e.g., "loading").Separator – A Visual DividerA Separator is a simple horizontal or vertical line used to group UI elements.
ttk.Separator(root, orient=tk.HORIZONTAL).pack(fill=tk.X, padx=10, pady=10)
Sizegrip – Resizing HandleA Sizegrip is a small triangular handle (usually at the bottom‑right corner) that allows the user to resize the window.
ttk.Sizegrip(root).pack(side=tk.BOTTOM, anchor=tk.SE)
Note: The window must be resizable (root.resizable(True, True)) for it to work.
Treeview – Multi‑Column List / TreeA Treeview is a powerful widget that can display hierarchical data (like a file explorer) or a simple multi‑column table (like a spreadsheet). It replaces Listbox and Canvas in some cases.
Basic Table Example:
tree = ttk.Treeview(root, columns=('ID', 'Name', 'Age'), show='headings')
tree.heading('ID', text='ID')
tree.heading('Name', text='Name')
tree.heading('Age', text='Age')
tree.insert('', tk.END, values=(1, 'Alice', 30))
tree.insert('', tk.END, values=(2, 'Bob', 25))
tree.pack()
# Get selected item
def on_select(event):
selected = tree.selection()
if selected:
item = tree.item(selected[0])
print(item['values'])
tree.bind('<<TreeviewSelect>>', on_select)
show='headings' – shows only the column headings (no tree lines).show='tree' – shows only tree structure (for file explorer).show='tree headings' – shows both.Tree Example (Hierarchical):
tree = ttk.Treeview(root)
tree.insert('', tk.END, text='Fruits', iid='fruits')
tree.insert('fruits', tk.END, text='Apple')
tree.insert('fruits', tk.END, text='Banana')
tree.pack()
Scrolling a Treeview:
scroll = ttk.Scrollbar(root, orient=tk.VERTICAL, command=tree.yview)
tree.configure(yscrollcommand=scroll.set)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
state() MethodTtk widgets have a .state() method that allows you to set or query the widget's state. States are strings like 'disabled', 'pressed', 'selected', 'active', 'focus', etc.
Setting states:
button.state(['disabled']) # Disable the button
button.state(['!disabled']) # Enable the button
button.state(['pressed']) # Simulate pressing
Querying states:
if 'disabled' in button.state():
print("Button is disabled")
instate() Method.instate(states, callback) checks if the widget is in a specific state and optionally calls a callback.
def on_button_state_change():
print("Button is now pressed")
button.instate(['pressed'], on_button_state_change)
.map()As shown earlier, .map() uses states to change appearance dynamically.
You can mix classic Tk widgets and Ttk widgets in the same application. They are fully compatible because both are built on the same Tk framework.
Best practice:
Canvas – for drawing.Text – for rich text editing (Ttk has no Text widget).Listbox – though Treeview is a good alternative.Menu – Ttk does not provide a themed menu; use tk.Menu.Toplevel – use tk.Toplevel.import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.title("Hybrid App")
# Ttk widgets for modern look
main_frame = ttk.Frame(root, padding=10)
main_frame.pack(fill=tk.BOTH, expand=True)
ttk.Label(main_frame, text="Name:").grid(row=0, column=0, sticky='w')
ttk.Entry(main_frame).grid(row=0, column=1, sticky='ew')
# Classic Canvas for drawing
canvas = tk.Canvas(main_frame, width=200, height=100, bg='white')
canvas.grid(row=1, column=0, columnspan=2, pady=10)
canvas.create_oval(50, 25, 150, 75, fill='red')
ttk.Button(main_frame, text="Submit").grid(row=2, column=0, columnspan=2)
root.mainloop()
Warning: Do not mix geometry managers (pack, grid, place) in the same container, regardless of whether they hold Ttk or classic widgets. The golden rule still applies.
| Scenario | Recommendation |
|---|---|
| Building a modern desktop application for end‑users. | Use Ttk for all supported widgets. |
| Need precise colour control (e.g., a custom colour‑coded dashboard). | Use classic Tk for those specific widgets, or use Ttk with Style if the theme supports it. |
| Need drawing (lines, shapes, images). | Use classic Canvas. |
| Need rich text editing. | Use classic Text. |
| Need a simple list. | Use ttk.Treeview (or tk.Listbox if you prefer). |
| Need drop‑down menus (Menu bar). | Use tk.Menu (Ttk has no replacement). |
| Need a tabbed interface. | Use ttk.Notebook – it's excellent. |
| Need a progress bar. | Use ttk.Progressbar. |
| Need a multi‑column table. | Use ttk.Treeview. |
| Need pop‑up windows. | Use tk.Toplevel (no Ttk equivalent). |
Let's combine everything into a single, cohesive application that showcases Ttk's power.
import tkinter as tk
from tkinter import ttk, messagebox
class ContactManager:
def __init__(self, root):
self.root = root
self.root.title("Contact Manager")
self.root.geometry("600x400")
# Style
self.style = ttk.Style()
self.style.theme_use('clam')
self.style.configure("TNotebook.Tab", padding=[10, 5])
self.style.map("TButton", foreground=[('active', 'blue')])
# Main notebook
self.notebook = ttk.Notebook(root)
self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# Tab 1: Contact List
self.list_tab = ttk.Frame(self.notebook)
self.notebook.add(self.list_tab, text="Contacts")
self.build_list_tab()
# Tab 2: Add Contact
self.add_tab = ttk.Frame(self.notebook)
self.notebook.add(self.add_tab, text="Add Contact")
self.build_add_tab()
# Bind tab change event
self.notebook.bind("<<NotebookTabChanged>>", self.on_tab_change)
def build_list_tab(self):
# Treeview for contacts
columns = ('ID', 'Name', 'Phone', 'Email')
self.tree = ttk.Treeview(self.list_tab, columns=columns, show='headings')
for col in columns:
self.tree.heading(col, text=col)
self.tree.column(col, width=100)
self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# Scrollbar
scroll = ttk.Scrollbar(self.list_tab, orient=tk.VERTICAL, command=self.tree.yview)
self.tree.configure(yscrollcommand=scroll.set)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
# Load sample data
sample = [(1, 'Alice', '123-456', 'alice@mail.com'),
(2, 'Bob', '789-012', 'bob@mail.com')]
for item in sample:
self.tree.insert('', tk.END, values=item)
# Bind double-click to view details
self.tree.bind("<Double-1>", self.view_contact)
def build_add_tab(self):
# Form using grid
fields = ['Name', 'Phone', 'Email']
self.entries = {}
for i, field in enumerate(fields):
ttk.Label(self.add_tab, text=f"{field}:").grid(row=i, column=0, sticky='e', padx=5, pady=5)
entry = ttk.Entry(self.add_tab, width=30)
entry.grid(row=i, column=1, padx=5, pady=5)
self.entries[field.lower()] = entry
# Buttons
btn_frame = ttk.Frame(self.add_tab)
btn_frame.grid(row=len(fields), column=0, columnspan=2, pady=10)
ttk.Button(btn_frame, text="Save", command=self.save_contact).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="Clear", command=self.clear_form).pack(side=tk.LEFT, padx=5)
def save_contact(self):
name = self.entries['name'].get()
phone = self.entries['phone'].get()
email = self.entries['email'].get()
if not name:
messagebox.showerror("Error", "Name is required!")
return
# Insert into tree
last_id = len(self.tree.get_children()) + 1
self.tree.insert('', tk.END, values=(last_id, name, phone, email))
messagebox.showinfo("Success", "Contact added!")
self.clear_form()
def clear_form(self):
for entry in self.entries.values():
entry.delete(0, tk.END)
def view_contact(self, event):
selected = self.tree.selection()
if selected:
values = self.tree.item(selected[0])['values']
messagebox.showinfo("Contact Details",
f"ID: {values[0]}\nName: {values[1]}\nPhone: {values[2]}\nEmail: {values[3]}")
def on_tab_change(self, event):
# Refresh when switching to list tab
if self.notebook.index(self.notebook.select()) == 0:
# Could reload data here
pass
if __name__ == "__main__":
root = tk.Tk()
app = ContactManager(root)
root.mainloop()
import tkinterfrom tkinter import ttkimport ttkfrom tkinter.ttk import *style.config("TButton", font=("Arial", 12))style.configure("TButton", font=("Arial", 12))style.set("TButton", font=("Arial", 12))ttk.Button.config(font=("Arial", 12))ttk.PanedWindowttk.Notebookttk.Treeviewttk.Comboboxtk.Listbox when you need multiple columns?
ttk.Comboboxttk.Treeviewttk.Listbox (Ttk has one)ttk.PanedWindowbutton.press()button.state(['pressed'])button.config(state='pressed')button.set_pressed(True)bg and fg options directly in their constructors..map() method on a ttk.Style object is used to define styles that change based on the widget's state.ttk.Progressbar has both 'determinate' and 'indeterminate' modes.ttk.Sizegrip allows the user to resize the window when placed at the bottom‑right corner.style.______('clam').ttk.________.ttk.Notebook is .______(child, text="...").ttk.Treeview, you use .______().'______'.Create a window with a ttk.Combobox listing all available themes, a ttk.Button labelled "Apply", and a selection of Ttk widgets (Button, Label, Entry, Progressbar). When "Apply" is clicked, change the theme to the selected one and show a messagebox confirming the change.
Build a ttk.Notebook with three tabs:
ttk.Progressbar and a "Start" button that increments the progress from 0 to 100% using .after().ttk.Treeview with three columns (Product, Price, Quantity) and at least 5 rows of sample data.ttk.Combobox (choose a colour), a ttk.Entry (enter text), and a "Submit" button that prints the values to the console.Create a custom style for TButton that:
active).pressed).
Apply this style to three buttons with different labels.Build a small drawing tool that uses:
ttk.Button for "Clear", ttk.Colorchooser? Actually, use Ttk for buttons and frames).tk.Canvas for drawing.ttk.Separator to separate the canvas from the controls.<B1-Motion> to draw on the canvas.Demonstrate your mastery of Ttk by building a modern task management application that leverages themes, styling, and new Ttk widgets.
Build a task manager with the following features:
Core Requirements:
ttk.Treeview with columns: ID, Task Name, Priority, Status (Pending/Done), Due Date.ttk.Progressbar at the bottom showing the percentage of tasks that are marked "Done".ttk.Entry widgets for: Task Name, Due Date.ttk.Combobox for Priority (Low, Medium, High).ttk.Checkbutton for "Mark as Done immediately?".Styling Requirements:
TNotebook.Tab to make the selected tab have a light blue background..map() on the Treeview rows to change the background colour of rows where the status is "Done" to light green.Theme Switching:
OptionMenu in the main window that lists at least 3 themes. When a theme is selected, apply it immediately to the entire application.Persistence (Bonus +5):
The following code attempts to style a Ttk button but contains three logical errors. Identify each error, explain the problem, and provide the corrected code.
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
style = ttk.Style()
style.theme_use('clam')
# Error 1: Setting bg directly on Ttk button
btn = ttk.Button(root, text="Click Me", bg="yellow", fg="blue")
btn.pack()
# Error 2: Incorrect style name
style.configure("TButton", font=("Arial", 12), background="yellow")
style.map("Custom.TButton", foreground=[('active', 'red')]) # Error 3: Trying to map a style that doesn't exist for the button
root.mainloop()
Corrected code:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
style = ttk.Style()
style.theme_use('clam')
style.configure("Custom.TButton", font=("Arial", 12), background="yellow") # background may be ignored
style.map("Custom.TButton", foreground=[('active', 'red')])
btn = ttk.Button(root, text="Click Me", style="Custom.TButton")
btn.pack()
root.mainloop()
ttk.Style.layout() (5 points)Research the .layout() method of the ttk.Style class. Write a short explanation (5‑7 sentences) of what it does, why it is considered advanced, and provide a simple example that changes the layout of a TButton (e.g., moving the label to the right of the image). Hint: Look up the default layout structure.
Write a short essay (6‑8 sentences) explaining why Ttk is considered a significant improvement over classic Tkinter widgets. Address:
In the "All Tasks" tab, add a right‑click context menu (tk.Menu) on the Treeview that provides options: "Mark as Done", "Delete Task", and "View Details". The "View Details" option should open a Toplevel window showing all details of the selected task in a formatted way.
| Term | Definition |
|---|---|
| Ttk (Themed Tkinter) | A submodule of Tkinter that provides themed, platform‑native widgets. |
| Theme | A collection of styles that define the appearance of all Ttk widgets. |
ttk.Style |
A class used to configure, map, and define custom styles for Ttk widgets. |
| Style Name | The identifier for a widget's style (e.g., "TButton", "Success.TButton"). |
.configure() |
Method on Style that sets the base appearance of a widget class. |
.map() |
Method on Style that defines appearance changes based on widget states. |
| State | A condition of a widget (e.g., 'disabled', 'active', 'pressed'). |
Combobox |
A Ttk widget combining an Entry and a dropdown list. |
Notebook |
A Ttk widget for tabbed containers. |
Progressbar |
A Ttk widget for showing progress (determinate or indeterminate). |
Treeview |
A Ttk widget for displaying hierarchical or tabular data. |
| Hybrid Application | An application that uses both classic Tk widgets and Ttk widgets. |
ttk::style, ttk::notebook, ttk::treeview.Text widget as classic Tk.This tutorial is designed to take approximately 3 hours of study, lab work, and homework. Mastering Ttk is the final step in becoming a proficient Tkinter developer—your applications will now look professional, modern, and native on every platform.
Previous | Tutorial index | | Next: none