From Tkinter to CustomTkinter: a migration guide

Posted 2026-04-21 on the pythondeck.com blog

If you have a working Tkinter app that looks stuck in 1998, CustomTkinter is the lowest-effort path to a modern look. The API is deliberately close to Tk's, but with rounded corners, theming, dark mode and HiDPI awareness.

What stays the same

Geometry managers (pack, grid, place) are unchanged. Event binding with .bind() is unchanged. The after() scheduler is unchanged. Anything outside the widget layer (file dialogs, message boxes, threading) stays exactly as you wrote it.

What changes

You import customtkinter as ctk instead of tkinter as tk for widgets. Class names get a CTk prefix: CTk, CTkButton, CTkEntry, CTkFrame, CTkLabel. Some option names differ slightly (e.g. fg_color instead of bg). Setting the theme is two lines at the top of your file:

import customtkinter as ctk
ctk.set_appearance_mode("system")   # 'light', 'dark', 'system'
ctk.set_default_color_theme("blue")

A minimal port

# before
import tkinter as tk
root = tk.Tk()
tk.Label(root, text="Hello").pack(padx=20, pady=20)
tk.Button(root, text="OK").pack(pady=8)
root.mainloop()

# after
import customtkinter as ctk
ctk.set_appearance_mode("system")
root = ctk.CTk()
ctk.CTkLabel(root, text="Hello").pack(padx=20, pady=20)
ctk.CTkButton(root, text="OK").pack(pady=8)
root.mainloop()

What to do about non-CTk widgets

Not every Tk widget has a CTk equivalent (notably Text, Canvas, Treeview). You can mix them - place a regular tk.Text inside a CTkFrame - but expect a small visual seam. For a fully unified look, wrap them in a frame and tint the surrounding chrome to match.

Designer support

Hand-porting a large UI by hand is tedious. A CustomTkinter-aware designer cuts this down to minutes: open the existing layout, swap widget classes in bulk, retheme, export.

« all blog posts Browse tutorials