2025, Sep 29 03:00

CTkButton icon not showing in CustomTkinter? Define the image at creation, then update with configure()

Troubleshooting CustomTkinter: fix a CTkButton icon not showing by initializing the image during button creation, then update via configure(image=...).

When you wire up icons for buttons in a CustomTkinter UI, everything looks straightforward until the images refuse to show up on CTkButton while the same CTkImage renders fine in other widgets like labels. If that sounds familiar, the root issue often turns out to be how the image parameter is initialized on the button itself.

Minimal example that reproduces the missing icon

import customtkinter as ctk
from PIL import Image
import os
from pathlib import Path
import tkinter
base_dir = Path.home() / "AppData" / "Local" / "TestApp"
icons_dir = os.path.join(base_dir, "Risorse", "icone")
pil_img = Image.open(os.path.join(icons_dir, "icona_cartelle.png"))
ctk_img = ctk.CTkImage(light_image=pil_img, dark_image=pil_img, size=(40, 40))
img_store = {"folder_with_items": ctk_img}
def make_button(container, label_text, row_index, col_index, pad_x):
    global img_store
    btn = ctk.CTkButton(master=container,
                        text=label_text,
                        compound="top",
                        fg_color="white",
                        text_color="black",
                        corner_radius=6,
                        cursor="hand2",
                        hover=True)
    btn._text_label.configure(wraplength=150)
    btn.grid(row=row_index, column=col_index, padx=pad_x, pady=5, sticky="w")
    btn.configure(image=img_store["folder_with_items"])  # image assigned after creation
    btn.assigned_img = img_store["folder_with_items"]
    print(btn.assigned_img)
    return btn
root = ctk.CTk()
make_button(root, "test", 2, 5, 5)
root.mainloop()

This setup loads a CTkImage once, stores it, and assigns it to a CTkButton after the widget is created. Despite being valid at first glance, the icon may not appear on the button, while the same image works for labels.

What’s actually happening

The behavior hinges on how the image parameter of CTkButton is defined. If the button is created without an initial image and you try to add one later with configure(image=...), you end up changing something that wasn’t defined at creation time. The result: the icon doesn’t show reliably on the button. In contrast, labels in the same code path render the image correctly, which narrows it down to how CTkButton handles its image parameter at initialization.

Fix: define the image when creating the button

The reliable approach is to set image during CTkButton construction. After that, calling configure(image=...) works as an update to an already defined parameter.

import customtkinter as ctk
from PIL import Image
import os
from pathlib import Path
import tkinter
base_dir = Path.home() / "AppData" / "Local" / "TestApp"
icons_dir = os.path.join(base_dir, "Risorse", "icone")
pil_img = Image.open(os.path.join(icons_dir, "icona_cartelle.png"))
ctk_img = ctk.CTkImage(light_image=pil_img, dark_image=pil_img, size=(40, 40))
img_store = {"folder_with_items": ctk_img}
def make_button(container, label_text, row_index, col_index, pad_x):
    global img_store
    btn = ctk.CTkButton(master=container,
                        text=label_text,
                        compound="top",
                        fg_color="white",
                        text_color="black",
                        corner_radius=6,
                        cursor="hand2",
                        hover=True,
                        image=img_store["folder_with_items"])  # image defined at creation
    btn._text_label.configure(wraplength=150)
    btn.grid(row=row_index, column=col_index, padx=pad_x, pady=5, sticky="w")
    # Later, you can still update it if needed
    # btn.configure(image=img_store["folder_with_items"]) 
    btn.assigned_img = img_store["folder_with_items"]
    print(btn.assigned_img)
    return btn
root = ctk.CTk()
make_button(root, "test", 2, 5, 5)
root.mainloop()

With this change, the button has a predefined image parameter. Any subsequent configure(image=...) targets a parameter that already exists, which makes the icon display consistently.

Why this matters

In UI code, the difference between initializing a widget with a parameter and trying to add it post-factum can be the line between predictable behavior and intermittent glitches. For CustomTkinter specifically, ensuring CTkButton is created with image lets you later adjust that image with configure() without fighting inconsistent rendering. This is especially relevant when you manage a pool of icons and reuse them across buttons and labels.

Takeaways

If a CTkButton doesn’t show its icon while the same CTkImage works in other widgets, initialize the button with image at creation time. After that, update the icon through configure(image=...) as needed. Keep your images accessible from a shared store so you can reuse them across the interface without duplicating loads.

The article is based on a question from StackOverflow by Andrea Buscetto and an answer by Andrea Buscetto.