Based on contributions by Erik_Endlich, Daan1.
A default Tkinter interface looks dated out of the box. This guide shows how to style plain Tkinter widgets with colors, fonts, and the ttk module, and how to get a modern look with much less effort using the CustomTkinter library. Read How to create a graphical interface in Python with Tkinter first if you haven’t built a basic Tkinter window yet.
What you need
- Python 3 with Tkinter (included in the standard library; on Linux install it with
sudo apt install python3-tkif missing). - For the CustomTkinter section:
pip install customtkinterand, if you want to display images,pip install pillow. - Patience — styling a GUI is iterative; expect to tweak colors and layout a few times.
Part 1: styling plain Tkinter
1. Set the background and text color
Use bg/background and fg/foreground to set a widget’s colors.
import tkinter as tk
root = tk.Tk()
button = tk.Button(root, text="Click Me", bg="blue", fg="white")
button.pack(pady=20)
root.mainloop()
2. Set a custom font
import tkinter as tk
root = tk.Tk()
label = tk.Label(root, text="Hello, Tkinter!", font=("Helvetica", 16, "bold"))
label.pack(pady=20)
root.mainloop()
3. Add padding and borders
padx/pady add space around a widget; relief and borderwidth control its border style.
import tkinter as tk
root = tk.Tk()
button = tk.Button(root, text="Click Me", relief="raised", borderwidth=5)
button.pack(pady=20)
root.mainloop()
4. Switch to ttk for modern-looking widgets
The ttk (themed Tkinter) module gives you better-looking, themeable widgets out of the box.
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
button = ttk.Button(root, text="Styled Button")
button.pack(pady=20)
root.mainloop()
5. Apply a built-in theme
ttk ships with a few built-in themes: "clam", "alt", "classic", "default", and more depending on your platform.
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
style = ttk.Style()
style.theme_use("clam")
button = ttk.Button(root, text="Styled Button")
button.pack(pady=20)
root.mainloop()
6. Customize a widget style with ttk.Style
For full control, define a named style and apply it to specific widgets. style.configure("TButton", ...) affects all ttk.Button widgets that use that style.
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
style = ttk.Style()
style.configure("TButton", font=("Verdana", 12), background="green", foreground="white")
button = ttk.Button(root, text="Styled Button", style="TButton")
button.pack(pady=20)
root.mainloop()
7. Put it together: a styled control panel
This combines a grid() layout, a custom ttk theme, two named button styles (green for normal actions, red for Stop), and hover colors via style.map(...).
import tkinter as tk
from tkinter import ttk
class Application:
def __init__(self, root):
self.root = root
self.root.title("Modern Tkinter Buttons")
self.top_frame = tk.Frame(self.root)
self.top_frame.grid(row=0, column=0, padx=20, pady=20)
self.style = ttk.Style()
self.style.theme_use("alt") # a more modern base theme
# style for normal action buttons
self.style.configure(
"TButton",
padding=6,
relief="raised",
background="#4CAF50", # green
foreground="white",
font=("Arial", 12, "bold"),
)
self.style.map("TButton", background=[("active", "#45a049")]) # hover color
# separate style for the Stop button
self.style.configure(
"RedButton.TButton",
padding=6,
relief="raised",
background="#FF5733", # red
foreground="white",
font=("Arial", 12, "bold"),
)
self.style.map("RedButton.TButton", background=[("active", "#FF2A00")])
# make columns/rows scale evenly
for col in range(4):
self.top_frame.grid_columnconfigure(col, weight=1)
for row in range(3):
self.top_frame.grid_rowconfigure(row, weight=1)
self.connect_button = ttk.Button(self.top_frame, text="Connect", command=self.connect)
self.connect_button.grid(row=0, column=0, columnspan=2, padx=5, pady=5, sticky="ew")
self.disconnect_button = ttk.Button(self.top_frame, text="Disconnect", command=self.disconnect)
self.disconnect_button.grid(row=0, column=2, columnspan=2, padx=5, pady=5, sticky="ew")
self.start_button = ttk.Button(self.top_frame, text="Start", command=self.start_process, state="disabled")
self.start_button.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky="ew")
self.pause_button = ttk.Button(self.top_frame, text="Pause", command=self.pause_process, state="disabled")
self.pause_button.grid(row=1, column=2, columnspan=2, padx=5, pady=5, sticky="ew")
self.stop_button = ttk.Button(
self.top_frame, text="Stop", command=self.stop_process, state="disabled", style="RedButton.TButton"
)
self.stop_button.grid(row=2, column=0, columnspan=4, padx=5, pady=5, sticky="ew")
def connect(self):
print("Connected")
self.start_button.config(state="normal")
self.pause_button.config(state="normal")
self.stop_button.config(state="normal")
def disconnect(self):
print("Disconnected")
self.start_button.config(state="disabled")
self.pause_button.config(state="disabled")
self.stop_button.config(state="disabled")
def start_process(self):
print("Process Started")
def pause_process(self):
print("Process Paused")
def stop_process(self):
print("Process Stopped")
root = tk.Tk()
app = Application(root)
root.mainloop()
Running this gives an interface with Connect/Disconnect buttons and Start/Pause/Stop buttons (Stop styled in red), like this:
Quick reference: common styling options
| Option | Description | Example |
|---|---|---|
bg / background |
Background color of the widget | bg="blue" |
fg / foreground |
Text color | fg="white" |
font |
Font type, size, and style | font=("Arial", 12, "bold") |
padx, pady |
Horizontal/vertical padding | padx=20, pady=10 |
borderwidth |
Border width | borderwidth=5 |
relief |
Border style (flat, raised, …) |
relief="raised" |
style |
Named ttk style to apply |
style="TButton" |
theme_use() |
Active ttk theme |
style.theme_use("clam") |
Further reading
- tkdocs.com — Tkinter examples and documentation.
- List of ttk themes
- W3Schools color picker — pick a color and copy its hex code.
Part 2: CustomTkinter for a modern look with less effort
CustomTkinter is a drop-in-style alternative to plain Tkinter that looks modern by default, without manually theming every widget. The API is close enough to Tkinter that the concepts from Part 1 (layout, styling widgets) still apply.
1. Install and import
pip install customtkinter
import customtkinter
2. Create the window
root = customtkinter.CTk()
3. Set the appearance mode and color theme
customtkinter.set_appearance_mode("dark") # or "light"
customtkinter.set_default_color_theme("green") # or "blue", "dark-blue"
4. Set the window size
Use a fixed size with geometry(), or make it fill the screen:
root.geometry("1920x1080")
# or, to go fullscreen:
# root.attributes("-fullscreen", True)
5. Lay out the interface
Start with a frame to hold your widgets, rather than placing everything directly into root — this keeps larger interfaces organized.
frame1 = customtkinter.CTkFrame(master=root)
You can place widgets with pack() for small interfaces, but for anything non-trivial, use grid(): it gives you rows and columns whose relative sizes you control with weights, so the layout scales cleanly to different screen resolutions (unlike fixed pixel width/height, which does not adapt).
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.grid_columnconfigure(1, weight=1)
root.grid_columnconfigure(2, weight=2)
root.grid_columnconfigure(3, weight=1)
frame1.grid(row=0, column=2)
Frames can themselves contain a grid of their own widgets, the same way.
6. Add a button
def button_function():
print("Hoisting mode activated")
hoist_button = customtkinter.CTkButton(
master=frame1,
corner_radius=0,
text="Hoisting mode",
font=("Arial", 14),
fg_color="#2b2b2b",
command=button_function,
)
hoist_button.grid(row=0, column=0, pady=(20, 0), padx=0)
command=button_function connects the button to the function that runs its logic — put your own actions inside button_function.
7. Add an image
from PIL import Image
light_image = Image.open("your_image.jpg")
my_image = customtkinter.CTkImage(light_image=light_image, size=(200, 200))
image_label = customtkinter.CTkLabel(frame1, image=my_image, text="")
image_label.image = my_image # keep a reference so the image isn't garbage-collected
image_label.grid(row=0, column=0, padx=(20, 0), sticky="")
Troubleshooting
ImportError/ “image not found” errors after installing Pillow. Restarting the terminal (or IDE) after installingpillowresolves this in most cases — the interpreter needs to pick up the newly installed package.- Image disappears right after being displayed. This happens when the
CTkImage/PhotoImageobject gets garbage-collected because nothing keeps a reference to it. Keep a reference on the widget itself, as inimage_label.image = my_imageabove. - Inconsistent variable names for the same frame (e.g.
Frame1in one place andframe1in another) will raise aNameError. Pick one name and use it consistently.
Related
Rewritten and consolidated (Sept 2026) from the original student how-to’s: How to style an interface using Tkinter, How to make an interface using custom tkinter.
