Tkinter Designer

CustomTkinter vs ttkbootstrap

Tkinter's default look is the thing people complain about first. It ships with Python, it works everywhere, and it looks like 1998. Two libraries fix that, and they do it in almost opposite ways, which is why "which is better" has no single answer, but "which is better for this app" usually does.

Both are widely used. At the time of writing customtkinter pulls over a million downloads a month from PyPI and ttkbootstrap around 190,000, so neither is a risky bet.

The core difference in one line

CustomTkinter replaces the widgets. ttkbootstrap re-themes the ones you already have.

CustomTkinter draws its own controls onto a canvas, so a CTkButton is not a tkinter button wearing a new coat; it's a custom-drawn widget with rounded corners, hover states and its own light and dark modes. ttkbootstrap goes the other way: it applies Bootstrap-inspired themes to ttk, the themed widget set already in Python's standard library, so you keep native widgets and native behaviour and simply change how they are painted.

That single architectural choice explains almost every practical difference below.

At a glance

CustomTkinterttkbootstrap
ApproachCustom-drawn widgetsThemes over native ttk
LookRounded, flat, distinctly modernBootstrap palettes, more conventional
Light / darkBuilt-in appearance modesPick a light or dark theme
Per-widget coloursConstructor optionsNamed ttk styles
Missing widgetsListbox, Spinbox, TreeviewText, Listbox, Canvas
SizingOn the constructorNormal geometry managers
Installpip install customtkinterpip install ttkbootstrap

Colours: the difference that bites first

This is where most people get stuck. In plain tkinter you colour a widget directly, with bg= and fg=:

self.title = tk.Label(root, text="Sign in", font=("Helvetica", 10, "bold"))
self.username = tk.Entry(root)
self.submit = tk.Button(root, text="Log in", command=self.on_submit,
                        fg="#ffffff", bg="#0d6efd")

CustomTkinter keeps that shape but renames things: fg_color is the widget's own fill and text_color is the text. Note that size moves onto the constructor too, because CTk rejects width and height in place():

self.title = ctk.CTkLabel(root, text="Sign in",
                          font=ctk.CTkFont(family="Helvetica", size=13, weight="bold"),
                          width=140, height=26)
self.username = ctk.CTkEntry(root, width=200, height=28)
self.submit = ctk.CTkButton(root, text="Log in", command=self.on_submit,
                            text_color="#ffffff", fg_color="#0d6efd",
                            width=110, height=32)

ttkbootstrap can't work that way. A ttk button rejects bg= and fg= outright, because its appearance belongs to a named style rather than to the widget. So a per-widget colour means defining a style for it:

def styled(name, **options):
    ttkb.Style().configure(name, **options)
    return name


self.title = ttkb.Label(root, text="Sign in",
                        style=styled("title.TLabel", font=("Helvetica", 10, "bold")))
self.username = ttkb.Entry(root)
self.submit = ttkb.Button(root, text="Log in", command=self.on_submit,
                          style=styled("submit.TButton",
                                       foreground="#ffffff", background="#0d6efd"))

None of that is a flaw; it's how ttk has always worked, and it pays off when you want one style applied consistently to fifty widgets. But it's the single biggest surprise for anyone arriving from plain tkinter, and it's worth knowing before you choose.

Which widgets you lose

Both libraries have gaps, and they're different gaps. CustomTkinter has no Listbox, Spinbox or Treeview, so a table or a numeric stepper falls back to the classic widget and keeps the old look. ttkbootstrap inherits ttk's omissions instead: no Text, no Listbox and no Canvas, so a multi-line editor or a drawing surface stays classic tkinter, though ttkbootstrap can still colour those through the option database.

If your app is a data table, ttkbootstrap gives you a properly themed Treeview and CustomTkinter does not. If your app is a text editor or a canvas drawing tool, CustomTkinter has native equivalents and ttkbootstrap does not. That one question often settles the choice on its own.

Two things that will catch you out

  • Progress bars use different ranges. ttk.Progressbar takes 0–100; CTkProgressBar takes 0.0–1.0. Moving an app across means rescaling every value you set.
  • CustomTkinter rejects .config() and insists on .configure(). ttk accepts both. It fails at runtime rather than when you write it, so it tends to surface as a crash on a button click.

So which should you use?

  • Want it to look unmistakably modern? CustomTkinter. Nothing else looks like it.
  • Building something with tables or lots of forms? ttkbootstrap, for its themed Treeview and native widget behaviour.
  • Need a text editor or canvas? CustomTkinter has both natively.
  • Want light and dark handled for you? CustomTkinter's appearance modes are the simplest route.
  • Care about native feel and accessibility? ttkbootstrap keeps real ttk widgets underneath.
  • Can't install anything? Neither, as both are third-party. Plain tkinter is the only one that ships with Python.

You don't have to decide up front

The code above isn't hand-written for this article; it's real output from this designer, which is the same three widgets exported three times. The toolkit is a project setting, so you can lay a window out once and switch between plain tkinter, CustomTkinter and ttkbootstrap without redrawing anything. The canvas repaints to match, and the generated Python changes with it.

That also makes it a quick way to answer the question for your own app: build the window, flip the toolkit, and look at both before committing. See the widget reference for how all twenty-five widgets map across the three, or the step-by-step walkthrough to build one from scratch.

Design once, export tkinter, CustomTkinter or ttkbootstrap.

Open the designer, no sign-up →