Custom Scrollable Frame Components
Create reusable scrollable components by extendingCTkScrollableFrame.
Scrollable CheckBox Frame
Build a custom scrollable frame containing multiple checkboxes with add/remove functionality.import customtkinter
class ScrollableCheckBoxFrame(customtkinter.CTkScrollableFrame):
def __init__(self, master, item_list, command=None, **kwargs):
super().__init__(master, **kwargs)
self.command = command
self.checkbox_list = []
for i, item in enumerate(item_list):
self.add_item(item)
def add_item(self, item):
checkbox = customtkinter.CTkCheckBox(self, text=item)
if self.command is not None:
checkbox.configure(command=self.command)
checkbox.grid(row=len(self.checkbox_list), column=0, pady=(0, 10))
self.checkbox_list.append(checkbox)
def remove_item(self, item):
for checkbox in self.checkbox_list:
if item == checkbox.cget("text"):
checkbox.destroy()
self.checkbox_list.remove(checkbox)
return
def get_checked_items(self):
return [checkbox.cget("text") for checkbox in self.checkbox_list if checkbox.get() == 1]
self.scrollable_checkbox_frame = ScrollableCheckBoxFrame(
master=self,
width=200,
command=self.checkbox_frame_event,
item_list=[f"item {i}" for i in range(50)]
)
self.scrollable_checkbox_frame.grid(row=0, column=0, padx=15, pady=15, sticky="ns")
self.scrollable_checkbox_frame.add_item("new item")
def checkbox_frame_event(self):
print(f"Selected: {self.scrollable_checkbox_frame.get_checked_items()}")
Scrollable RadioButton Frame
Create a scrollable frame with radio buttons for selecting a single option from many.class ScrollableRadiobuttonFrame(customtkinter.CTkScrollableFrame):
def __init__(self, master, item_list, command=None, **kwargs):
super().__init__(master, **kwargs)
self.command = command
self.radiobutton_variable = customtkinter.StringVar()
self.radiobutton_list = []
for i, item in enumerate(item_list):
self.add_item(item)
def add_item(self, item):
radiobutton = customtkinter.CTkRadioButton(
self,
text=item,
value=item,
variable=self.radiobutton_variable
)
if self.command is not None:
radiobutton.configure(command=self.command)
radiobutton.grid(row=len(self.radiobutton_list), column=0, pady=(0, 10))
self.radiobutton_list.append(radiobutton)
def remove_item(self, item):
for radiobutton in self.radiobutton_list:
if item == radiobutton.cget("text"):
radiobutton.destroy()
self.radiobutton_list.remove(radiobutton)
return
def get_checked_item(self):
return self.radiobutton_variable.get()
Scrollable Label-Button Frame
Combine labels and buttons in a scrollable list.class ScrollableLabelButtonFrame(customtkinter.CTkScrollableFrame):
def __init__(self, master, command=None, **kwargs):
super().__init__(master, **kwargs)
self.grid_columnconfigure(0, weight=1)
self.command = command
self.radiobutton_variable = customtkinter.StringVar()
self.label_list = []
self.button_list = []
def add_item(self, item, image=None):
label = customtkinter.CTkLabel(
self,
text=item,
image=image,
compound="left",
padx=5,
anchor="w"
)
button = customtkinter.CTkButton(self, text="Command", width=100, height=24)
if self.command is not None:
button.configure(command=lambda: self.command(item))
label.grid(row=len(self.label_list), column=0, pady=(0, 10), sticky="w")
button.grid(row=len(self.button_list), column=1, pady=(0, 10), padx=5)
self.label_list.append(label)
self.button_list.append(button)
def remove_item(self, item):
for label, button in zip(self.label_list, self.button_list):
if item == label.cget("text"):
label.destroy()
button.destroy()
self.label_list.remove(label)
self.button_list.remove(button)
return
Custom scrollable frames automatically handle scrolling when content exceeds the frame height.
Advanced Image Handling
UseCTkImage for advanced image features like appearance-aware images and dynamic scaling.
Loading Images with CTkImage
import customtkinter
import os
from PIL import Image
class App(customtkinter.CTk):
def __init__(self):
super().__init__()
image_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test_images")
# Simple image with size
self.logo_image = customtkinter.CTkImage(
Image.open(os.path.join(image_path, "logo.png")),
size=(26, 26)
)
# Large display image
self.large_test_image = customtkinter.CTkImage(
Image.open(os.path.join(image_path, "large_test_image.png")),
size=(500, 150)
)
Appearance Mode-Aware Images
Provide different images for light and dark modes.# Image that changes based on appearance mode
self.home_image = customtkinter.CTkImage(
light_image=Image.open(os.path.join(image_path, "home_dark.png")),
dark_image=Image.open(os.path.join(image_path, "home_light.png")),
size=(20, 20)
)
self.chat_image = customtkinter.CTkImage(
light_image=Image.open(os.path.join(image_path, "chat_dark.png")),
dark_image=Image.open(os.path.join(image_path, "chat_light.png")),
size=(20, 20)
)
Images automatically switch when the user changes appearance mode. No additional code required!
Using Images in Widgets
# Label with image and text
self.navigation_frame_label = customtkinter.CTkLabel(
self.navigation_frame,
text=" Image Example",
image=self.logo_image,
compound="left",
font=customtkinter.CTkFont(size=15, weight="bold")
)
self.navigation_frame_label.grid(row=0, column=0, padx=20, pady=20)
# Button with image only
self.home_frame_button_1 = customtkinter.CTkButton(
self.home_frame,
text="",
image=self.image_icon_image
)
# Button with image and text (different compound options)
self.home_frame_button_2 = customtkinter.CTkButton(
self.home_frame,
text="CTkButton",
image=self.image_icon_image,
compound="right" # Options: "left", "right", "top", "bottom"
)
self.home_frame_button_2.grid(row=2, column=0, padx=20, pady=10)
self.home_frame_button_3 = customtkinter.CTkButton(
self.home_frame,
text="CTkButton",
image=self.image_icon_image,
compound="top"
)
self.home_frame_button_3.grid(row=3, column=0, padx=20, pady=10)
self.home_frame_button_4 = customtkinter.CTkButton(
self.home_frame,
text="CTkButton",
image=self.image_icon_image,
compound="bottom",
anchor="w"
)
self.home_frame_button_4.grid(row=4, column=0, padx=20, pady=10)
Multi-Frame Navigation
Implement frame switching for multi-page applications.Create navigation frame with buttons
# Create navigation frame
self.navigation_frame = customtkinter.CTkFrame(self, corner_radius=0)
self.navigation_frame.grid(row=0, column=0, sticky="nsew")
self.navigation_frame.grid_rowconfigure(4, weight=1)
# Add navigation buttons
self.home_button = customtkinter.CTkButton(
self.navigation_frame,
corner_radius=0,
height=40,
border_spacing=10,
text="Home",
fg_color="transparent",
text_color=("gray10", "gray90"),
hover_color=("gray70", "gray30"),
image=self.home_image,
anchor="w",
command=self.home_button_event
)
self.home_button.grid(row=1, column=0, sticky="ew")
self.frame_2_button = customtkinter.CTkButton(
self.navigation_frame,
corner_radius=0,
height=40,
border_spacing=10,
text="Frame 2",
fg_color="transparent",
text_color=("gray10", "gray90"),
hover_color=("gray70", "gray30"),
image=self.chat_image,
anchor="w",
command=self.frame_2_button_event
)
self.frame_2_button.grid(row=2, column=0, sticky="ew")
Create content frames
# Create home frame
self.home_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color="transparent")
self.home_frame.grid_columnconfigure(0, weight=1)
# Create second frame
self.second_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color="transparent")
# Create third frame
self.third_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color="transparent")
# Select default frame
self.select_frame_by_name("home")
Implement frame switching logic
def select_frame_by_name(self, name):
# Set button color for selected button
self.home_button.configure(
fg_color=("gray75", "gray25") if name == "home" else "transparent"
)
self.frame_2_button.configure(
fg_color=("gray75", "gray25") if name == "frame_2" else "transparent"
)
self.frame_3_button.configure(
fg_color=("gray75", "gray25") if name == "frame_3" else "transparent"
)
# Show selected frame
if name == "home":
self.home_frame.grid(row=0, column=1, sticky="nsew")
else:
self.home_frame.grid_forget()
if name == "frame_2":
self.second_frame.grid(row=0, column=1, sticky="nsew")
else:
self.second_frame.grid_forget()
if name == "frame_3":
self.third_frame.grid(row=0, column=1, sticky="nsew")
else:
self.third_frame.grid_forget()
def home_button_event(self):
self.select_frame_by_name("home")
def frame_2_button_event(self):
self.select_frame_by_name("frame_2")
def frame_3_button_event(self):
self.select_frame_by_name("frame_3")
Background Images
Create applications with full-window background images.import customtkinter
from PIL import Image
import os
class App(customtkinter.CTk):
width = 900
height = 600
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.title("CustomTkinter Background Image")
self.geometry(f"{self.width}x{self.height}")
self.resizable(False, False)
# Load and create background image
current_path = os.path.dirname(os.path.realpath(__file__))
self.bg_image = customtkinter.CTkImage(
Image.open(current_path + "/test_images/bg_gradient.jpg"),
size=(self.width, self.height)
)
self.bg_image_label = customtkinter.CTkLabel(self, image=self.bg_image)
self.bg_image_label.grid(row=0, column=0)
# Create login frame on top of background
self.login_frame = customtkinter.CTkFrame(self, corner_radius=0)
self.login_frame.grid(row=0, column=0, sticky="ns")
Place the background image label first, then overlay other frames on top using the same grid cell.
Login/Main Frame Switching
Implement authentication flows with frame switching.def __init__(self):
# ... background setup ...
# Create login frame
self.login_frame = customtkinter.CTkFrame(self, corner_radius=0)
self.login_frame.grid(row=0, column=0, sticky="ns")
self.login_label = customtkinter.CTkLabel(
self.login_frame,
text="CustomTkinter\nLogin Page",
font=customtkinter.CTkFont(size=20, weight="bold")
)
self.login_label.grid(row=0, column=0, padx=30, pady=(150, 15))
self.username_entry = customtkinter.CTkEntry(
self.login_frame,
width=200,
placeholder_text="username"
)
self.username_entry.grid(row=1, column=0, padx=30, pady=(15, 15))
self.password_entry = customtkinter.CTkEntry(
self.login_frame,
width=200,
show="*",
placeholder_text="password"
)
self.password_entry.grid(row=2, column=0, padx=30, pady=(0, 15))
self.login_button = customtkinter.CTkButton(
self.login_frame,
text="Login",
command=self.login_event,
width=200
)
self.login_button.grid(row=3, column=0, padx=30, pady=(15, 15))
# Create main frame
self.main_frame = customtkinter.CTkFrame(self, corner_radius=0)
self.main_frame.grid_columnconfigure(0, weight=1)
self.main_label = customtkinter.CTkLabel(
self.main_frame,
text="CustomTkinter\nMain Page",
font=customtkinter.CTkFont(size=20, weight="bold")
)
self.main_label.grid(row=0, column=0, padx=30, pady=(30, 15))
self.back_button = customtkinter.CTkButton(
self.main_frame,
text="Back",
command=self.back_event,
width=200
)
self.back_button.grid(row=1, column=0, padx=30, pady=(15, 15))
def login_event(self):
print("Login pressed - username:", self.username_entry.get(),
"password:", self.password_entry.get())
self.login_frame.grid_forget() # Remove login frame
self.main_frame.grid(row=0, column=0, sticky="nsew", padx=100) # Show main frame
def back_event(self):
self.main_frame.grid_forget() # Remove main frame
self.login_frame.grid(row=0, column=0, sticky="ns") # Show login frame
Using CTkFont for Custom Typography
# Create custom fonts
title_font = customtkinter.CTkFont(size=20, weight="bold")
label_font = customtkinter.CTkFont(size=15, weight="bold")
# Apply to widgets
self.logo_label = customtkinter.CTkLabel(
self.sidebar_frame,
text="CustomTkinter",
font=title_font
)
self.navigation_frame_label = customtkinter.CTkLabel(
self.navigation_frame,
text=" Image Example",
image=self.logo_image,
compound="left",
font=label_font
)
Font sizes in CustomTkinter v5.0.0+ are measured in pixels, not points. Adjust your sizes accordingly when migrating from older versions.
Combining Multiple Advanced Features
Here’s an example combining scrollable frames, images, and custom components:current_dir = os.path.dirname(os.path.abspath(__file__))
self.scrollable_label_button_frame = ScrollableLabelButtonFrame(
master=self,
width=300,
command=self.label_button_frame_event,
corner_radius=0
)
self.scrollable_label_button_frame.grid(row=0, column=2, padx=0, pady=0, sticky="nsew")
# Add items with images
for i in range(20):
self.scrollable_label_button_frame.add_item(
f"image and item {i}",
image=customtkinter.CTkImage(
Image.open(os.path.join(current_dir, "test_images", "chat_light.png"))
)
)
def label_button_frame_event(self, item):
print(f"label button frame clicked: {item}")
Dynamic Widget Resizing
Control widget dimensions and resizing behavior.# Create with initial size
self.scrollable_radiobutton_frame = ScrollableRadiobuttonFrame(
master=self,
width=500,
command=self.radiobutton_frame_event,
item_list=[f"item {i}" for i in range(100)],
label_text="ScrollableRadiobuttonFrame"
)
self.scrollable_radiobutton_frame.grid(row=0, column=1, padx=15, pady=15, sticky="ns")
# Reconfigure width dynamically
self.scrollable_radiobutton_frame.configure(width=200)
Complete Advanced Example
Complete Advanced Example
import customtkinter
import os
from PIL import Image
class AdvancedApp(customtkinter.CTk):
def __init__(self):
super().__init__()
self.title("Advanced CustomTkinter App")
self.grid_rowconfigure(0, weight=1)
self.columnconfigure(2, weight=1)
customtkinter.set_appearance_mode("dark")
# Create scrollable checkbox frame
self.scrollable_checkbox_frame = ScrollableCheckBoxFrame(
master=self,
width=200,
command=self.checkbox_frame_event,
item_list=[f"item {i}" for i in range(50)]
)
self.scrollable_checkbox_frame.grid(row=0, column=0, padx=15, pady=15, sticky="ns")
# Create scrollable radiobutton frame
self.scrollable_radiobutton_frame = ScrollableRadiobuttonFrame(
master=self,
width=500,
command=self.radiobutton_frame_event,
item_list=[f"item {i}" for i in range(100)],
label_text="Options"
)
self.scrollable_radiobutton_frame.grid(row=0, column=1, padx=15, pady=15, sticky="ns")
# Create scrollable label and button frame with images
current_dir = os.path.dirname(os.path.abspath(__file__))
self.scrollable_label_button_frame = ScrollableLabelButtonFrame(
master=self,
width=300,
command=self.label_button_frame_event,
corner_radius=0
)
self.scrollable_label_button_frame.grid(row=0, column=2, padx=0, pady=0, sticky="nsew")
for i in range(20):
self.scrollable_label_button_frame.add_item(
f"item {i}",
image=customtkinter.CTkImage(
Image.open(os.path.join(current_dir, "test_images", "icon.png"))
)
)
def checkbox_frame_event(self):
print(f"checkbox frame: {self.scrollable_checkbox_frame.get_checked_items()}")
def radiobutton_frame_event(self):
print(f"radiobutton frame: {self.scrollable_radiobutton_frame.get_checked_item()}")
def label_button_frame_event(self, item):
print(f"button clicked: {item}")
if __name__ == "__main__":
app = AdvancedApp()
app.mainloop()