Tkinter OptionMenu Tutorial
Learn to build dropdowns with Tkinter OptionMenu: callbacks, live UI updates, adding/removing options, styling tips, and a complete runnable theme selector example.
Tutorial Progress
1 Introduction to Tkinter OptionMenu
OptionMenu in Tkinter creates a dropdown list bound to a variable. It is handy for choices like themes, languages, or any finite set of options. Under the hood, it stores the selected value in a variable and can call a function whenever the user selects a new item.
By the end of this guide, you’ll have a runnable theme selector app that updates a preview pane and displays a message with the selected theme.
2 Prerequisites: Python and Tkinter
You’ll need Python 3.7+. Tkinter ships with most Python installations on macOS and Windows. If you’re on Linux, install the tk package:
# Debian/Ubuntu
sudo apt-get install python3-tk
Verify Tkinter is available:
python -m tkinter
If a small window opens, Tkinter is working.
3 Minimal OptionMenu: The Core Pieces
You need three building blocks: a Tkinter variable (usually StringVar), a list of options, and an OptionMenu connected to the variable.
import tkinter as tk
root = tk.Tk()
root.title("Minimal OptionMenu")
# 1) Variable to hold the current selection
choice = tk.StringVar(value="A")
# 2) Create the OptionMenu, passing the variable and the options
menu = tk.OptionMenu(root, choice, "A", "B", "C")
menu.pack(padx=20, pady=20)
root.mainloop()
- StringVar keeps the current value in sync with the dropdown.
value="A"sets the default; without it, the first option is usually selected.- Updating
choice.set("B")will move the OptionMenu selection programmatically.
4 React to Changes: Add a Callback
Pass a function to the command argument. Tkinter calls it with the newly selected value.
import tkinter as tk
from tkinter import messagebox
def on_select(value):
messagebox.showinfo("Selection", f"You picked: {value}")
root = tk.Tk()
choice = tk.StringVar(value="Ocean")
menu = tk.OptionMenu(root, choice, "Ocean", "Forest", "Sunset", command=on_select)
menu.pack(padx=20, pady=20)
root.mainloop()
Use callbacks to sync other widgets, trigger validation, or update an information panel.
5 Build a Live Preview (Like Our Example)
Store theme data, then update a preview pane whenever the selection changes.
import tkinter as tk
from tkinter import messagebox
themes = {
"Ocean": {"primary": "#1a73e8", "secondary": "#4285f4", "text": "white"},
"Forest": {"primary": "#2e7d32", "secondary": "#4caf50", "text": "white"},
"Sunset": {"primary": "#d32f2f", "secondary": "#f44336", "text": "white"},
}
def update_preview(theme_name):
t = themes[theme_name]
preview.configure(bg=t["primary"])
title.configure(bg=t["primary"], fg=t["text"])
button.configure(bg=t["secondary"], fg=t["text"], activebackground=t["primary"], activeforeground=t["text"])
root = tk.Tk()
choice = tk.StringVar(value="Ocean")
menu = tk.OptionMenu(root, choice, *themes.keys(), command=lambda v: update_preview(v))
menu.pack(padx=20, pady=10)
preview = tk.Frame(root, width=220, height=120, relief="raised", bd=2)
preview.pack(pady=10)
title = tk.Label(preview, text="Theme Preview")
title.pack(pady=6)
button = tk.Button(preview, text="OK", command=lambda: messagebox.showinfo("Theme", f"{choice.get()}"))
button.pack(pady=6)
update_preview(choice.get())
root.mainloop()
6 Changing Options and Disabling Items
OptionMenu does not expose a built-in remove/add API. The usual approach is to rebuild the menu with the updated options and preserve the current selection.
def rebuild_options(new_options, keep_value=True):
current = choice.get() if keep_value else new_options[0]
menu["menu"].delete(0, "end")
for opt in new_options:
menu["menu"].add_command(label=opt, command=lambda v=opt: choice.set(v))
choice.set(current)
# Start with these
choices = ["A", "B", "C"]
choice = tk.StringVar(value="B")
menu = tk.OptionMenu(root, choice, *choices)
menu.pack(padx=20, pady=20)
# Later, remove "B"
rebuild_options([opt for opt in choices if opt != "B"])
To disable a specific item, wrap it in a cascade submenu and use state="disabled". Removing or reordering is simpler with a rebuild.
7 Styling and Layout: Taming the Look
Use configure(width=...) to control width. For fonts and colors, set attributes on the menu and its internal menu widget. Combining ttk.Frame with tk.OptionMenu can tidy spacing.
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
frame = ttk.Frame(root, padding=16)
frame.pack()
choice = tk.StringVar(value="Ocean")
menu = tk.OptionMenu(frame, choice, "Ocean", "Forest", "Sunset")
menu.configure(width=14, font=("Helvetica", 11))
menu["menu"].configure(font=("Helvetica", 11))
ttk.Label(frame, text="Choose a theme:").pack(anchor="w", pady=(0, 6))
menu.pack(anchor="w")
root.mainloop()
8 Common Pitfalls and Gotchas
- Forgetting to set an initial value can result in an empty selection.
- Callbacks receive the new value, not the variable. Use the variable if you need broader state.
- OptionMenu does not have a direct “remove option” API—rebuild via the inner
menuwidget. - Mixing
tkandttkcan produce subtle style differences. - On some systems, changing fonts on the inner menu has no effect due to platform rendering.
9 Exercise: Add and Remove Options
Extend the minimal example: add an entry field and a button that inserts the typed option into the menu (if not present) and selects it. Then add another button to remove the currently selected option.
import tkinter as tk
root = tk.Tk()
choice = tk.StringVar(value="A")
options = ["A", "B", "C"]
menu = tk.OptionMenu(root, choice, *options)
menu.pack(padx=16, pady=16)
entry = tk.Entry(root)
entry.pack(padx=16)
def add_option():
val = entry.get().strip()
if not val or val in options:
return
options.append(val)
menu["menu"].add_command(label=val, command=lambda v=val: choice.set(val))
choice.set(val)
def remove_selected():
val = choice.get()
if val not in options:
return
options.remove(val)
rebuild_options()
tk.Button(root, text="Add", command=add_option).pack(pady=6)
tk.Button(root, text="Remove Selected", command=remove_selected).pack(pady=6)
def rebuild_options():
current = choice.get()
menu["menu"].delete(0, "end")
for opt in options:
menu["menu"].add_command(label=opt, command=lambda v=opt: choice.set(v))
if options:
choice.set(current if current in options else options[0])
root.mainloop()
10 Full Project: Theme Selector with Preview
Copy this into a file named theme_selector.py and run with python theme_selector.py.
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
class ColorThemeSelector(tk.Tk):
def __init__(self):
super().__init__()
# Configure the main window
self.title("OptionMenu Example")
self.geometry("400x300")
self.configure(padx=20, pady=20)
# Define color themes
self.color_themes = {
"Ocean": {"primary": "#1a73e8", "secondary": "#4285f4", "text": "white"},
"Forest": {"primary": "#2e7d32", "secondary": "#4caf50", "text": "white"},
"Sunset": {"primary": "#d32f2f", "secondary": "#f44336", "text": "white"},
"Lavender": {"primary": "#7b1fa2", "secondary": "#9c27b0", "text": "white"}
}
# Create and configure a frame
self.main_frame = ttk.Frame(self)
self.main_frame.pack(expand=True, fill='both')
# Create a StringVar to store the selected option
self.selected_theme = tk.StringVar()
self.selected_theme.set("Ocean") # Set default value
# Create a label
self.label = ttk.Label(
self.main_frame,
text="Select Your Color Theme:",
font=('Helvetica', 12)
)
self.label.pack(pady=10)
# Create the OptionMenu
self.theme_menu = tk.OptionMenu(
self.main_frame,
self.selected_theme,
*self.color_themes.keys(),
command=self.update_preview
)
self.theme_menu.configure(width=15)
self.theme_menu.pack(pady=10)
# Create a preview frame
self.preview_frame = tk.Frame(
self.main_frame,
width=200,
height=100,
relief="raised",
bd=2
)
self.preview_frame.pack(pady=20)
# Create preview elements
self.preview_title = tk.Label(
self.preview_frame,
text="Theme Preview",
font=('Helvetica', 10)
)
self.preview_title.pack(pady=5)
self.preview_button = tk.Button(
self.preview_frame,
text="Sample Button",
command=self.show_selection
)
self.preview_button.pack(pady=10)
# Initialize the preview
self.update_preview()
def update_preview(self, *args):
"""Update the preview frame with the selected theme colors"""
theme = self.color_themes[self.selected_theme.get()]
# Update preview frame background
self.preview_frame.configure(bg=theme["primary"])
self.preview_title.configure(
bg=theme["primary"],
fg=theme["text"]
)
self.preview_button.configure(
bg=theme["secondary"],
fg=theme["text"],
activebackground=theme["primary"],
activeforeground=theme["text"]
)
def show_selection(self):
"""Show a message box with the current selection"""
messagebox.showinfo(
"Selected Theme",
f"You have selected the {self.selected_theme.get()} theme!"
)
if __name__ == "__main__":
app = ColorThemeSelector()
app.mainloop()
Run it: python theme_selector.py. Try changing themes—watch the preview update in real time.