How to style a Tkinter interface, and how to switch to CustomTkinter

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-tk if missing).
  • For the CustomTkinter section: pip install customtkinter and, 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

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 installing pillow resolves 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/PhotoImage object gets garbage-collected because nothing keeps a reference to it. Keep a reference on the widget itself, as in image_label.image = my_image above.
  • Inconsistent variable names for the same frame (e.g. Frame1 in one place and frame1 in another) will raise a NameError. 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.