Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
344b30e
fixed scrollable frame mouse wheel on linux #1356
TomSchimansky Jul 11, 2023
cd8d7be
Merge branch 'master' into develop
TomSchimansky Dec 28, 2025
9420ac3
Enhance README with developer info and formatting
TomSchimansky Dec 28, 2025
9a9453a
add dev notes
TomSchimansky Dec 28, 2025
b7bd7af
Merge remote-tracking branch 'origin/develop' into develop
TomSchimansky Dec 28, 2025
a6e0722
Created Showroom App
FedericoSpada Jan 1, 2026
ebd3b71
fix: Add missing scaling base class destroy in dropdown_menu. (#2772)
qwertyzen Jan 1, 2026
2e11a1e
Update Readme.md to answer issues/2668 (#2705)
lucaheyworth Jan 2, 2026
764a302
update outdated requirements.txt (#2571)
HelloWorld-er Jan 2, 2026
76a10bd
Widget-scaled int values are cast back to int (#2468)
ElSaico Jan 3, 2026
5c77c99
fix scrollable frame destroy() (#2352)
PiesArentSquare Jan 3, 2026
27db1bd
Improved renaming method in the CTkTabview class (#2256)
JanPanthera Jan 3, 2026
cea11a0
Add segmented_button_font option to CTkTabview
bibo19842003 Jan 4, 2026
7532484
Add segmented_button_font option to CTkTabview
FedericoSpada Jan 4, 2026
9ad9495
fixed an exception when destroying a dropdown menu, then rescaling th…
Nerogar Jan 4, 2026
8ff5d94
CtkCanvas.coords add return values (fixes #1419) (#2240)
DerSchinken Jan 4, 2026
84222ab
Fix #1511 and #2160: Toplevel icon overriding on init (#2162)
timgdx Jan 4, 2026
907300e
Added Scrollbar drag offset (#2158)
timgdx Jan 5, 2026
2070277
Quick fix of set text_color operation (#2078)
Alzurek Jan 5, 2026
b2bb1e0
Fix `text_color_disabled` of `CTkLabel`. (#2063)
solicomo Jan 5, 2026
f81cd8d
Reset Minsize for CTkButton (#1931)
DragonOfShuu Jan 5, 2026
f159a25
Add an orientation option to CTkSegmentedButton (#2333)
PhilipNelson5 Jan 6, 2026
7bb5610
Added new methods to ctk_tabview.py (#1428)
fgosdrauka Jan 6, 2026
f605aba
Fix typo bug causing app slowdown on multiple refreshes by preventing…
darkManPi Jan 9, 2026
a0a6496
Fix .delete() to destroy ctkframe fixes#1046 (#1083)
ElectricCandlelight Jan 11, 2026
8d62feb
Improved/fixed cget() method for all widgets
FedericoSpada Jan 11, 2026
cb7347b
Added border to CTkLabel
FedericoSpada Jan 11, 2026
6a5460b
Added MouseWheel detection to CTkSlider and fixed it on CTkScrollbar
FedericoSpada Jan 11, 2026
db08925
Improved/fixed configure() method for all widgets
FedericoSpada Jan 13, 2026
b33e220
Added new utility methods to all widgets
FedericoSpada Jan 15, 2026
73ca84f
Improved user experience
FedericoSpada Jan 17, 2026
73bc7ad
Recreated 3 Pull Requests that weren't perfect
FedericoSpada Jan 18, 2026
51b3435
Updated CHANGELOG in preparation for the release
FedericoSpada Jan 18, 2026
8c85d9b
Fixed some bugs with the previous changes
FedericoSpada Jan 21, 2026
a796f6e
Bump to 5.3.0
FedericoSpada Jan 21, 2026
c12c9ab
Improved scroll behavior for CTkScrollableFrame
FedericoSpada Feb 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,4 @@ cython_debug/
documentation_images/customtkinter_design.afdesign
images/
Readme_pypi.md
temp.py
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,26 @@ ToDo:
- set icon (self.call("wm", "iconphoto", self._w, tkinter.PhotoImage(file="test_images/CustomTkinter_logo_single.png")))
- add option to change label position for checkbox, switch, radiobutton #628

## [5.3.0] - 2026-01-21
### Added
- Showroom App, immediately available with the library installation
- Gold theme
- set(), index(), len() methods for those widgets that were suitable to use them
- Attribute to CTkSegmentedButton to make it vertically instead of horizontally
- Attribute to CTkTabview to configure the font for its CTkSegmentedButton
- Possibility to add a border to CTkLabel
- Mouse Wheel detection to CTkSlider and improved it on CTkScrollbar

### Changed
- CTkButton triggers the command when the Mouse Button is released
- CTkEntry and CTkTextbox lose focus when you click somewhere else
- Clicking the Dropdown button again closes the menu for CTkComboBox and CTkOptionMenu
- Improved/fixed configure() and cget() methods for all widgets
- Improved Tab renaming for CTkTabview
- Improved drag behavior for CTkScrollbar
- Properly managed borders for CTkScrollableFrame
- Fixed a bug that prevented setting a custom Icon for CTkToplevel
- Fixed many bugs related to missing invocations or wrong names

## [5.2.0] - 2022-05-02
### Added
Expand Down
29 changes: 20 additions & 9 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,33 @@
</p>

<div align="center">

![PyPI](https://img.shields.io/pypi/v/customtkinter)
![PyPI - Downloads](https://img.shields.io/pypi/dm/customtkinter?color=green&label=downloads)
![Downloads last 6 month](https://static.pepy.tech/personalized-badge/customtkinter?period=total&units=international_system&left_color=grey&right_color=green&left_text=total%20downloads)
![PyPI - License](https://img.shields.io/badge/license-MIT-blue)

</div>
![Minimum Python Version](https://shields.io/badge/Minimum_Python-3.7-blue)

---

<div align="center">
| Current Developers | Contact |
|----------------------------------------------|-|
| Tom Schimansky | contact@customtkinter.tomschimansky.com |
| Federico Spada | www.linkedin.com/in/federicospada13 |

<a href="https://www.paypal.com/donate/?hosted_button_id=LK5QAZYRN2R2A"><img src="documentation_images/paypal_donate_button.png" width=170 alt="Paypal donation button"></a>

<a></a>

| Massive Thanks to all the People who Donated to help this Project 😇 |
|----------------------------------------------|
</div>

<h3>
---

<div align="center">

<h2>
Official website: https://customtkinter.tomschimansky.com/
</h3>
</h2>

</div>

CustomTkinter is a python UI-library based on Tkinter, which provides new, modern and
Expand Down Expand Up @@ -88,6 +94,11 @@ can find more example programs and in the [Documentation](https://github.com/Tom
you can find further information on the appearance mode, scaling, themes and all widgets.

## More Examples and Showcase
You can run the following code to show a simple App that displays all available widgets:
```python
import customtkinter as ctk
ctk.run_showroom()
```

### Appearance mode change and scaling change

Expand Down
228 changes: 227 additions & 1 deletion customtkinter/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
__version__ = "5.2.2"
__version__ = "5.3.0"

from typing import Optional
import os
import sys
from tkinter import Variable, StringVar, IntVar, DoubleVar, BooleanVar
Expand Down Expand Up @@ -86,3 +87,228 @@ def deactivate_automatic_dpi_awareness():

def set_ctk_parent_class(ctk_parent_class):
ctk_tk.CTK_PARENT_CLASS = ctk_parent_class


def run_showroom() -> None:
set_appearance_mode("Light")
set_default_color_theme("blue")

new_instance: bool = True
while new_instance:
app = _Showroom()
app.mainloop()
new_instance = app.new_instance_requested


class _Showroom(CTk):
SPACING = 20

def __init__(self) -> None:
super().__init__()

# configure window
self.title("CustomTkinter Showroom")

self.new_instance_requested: bool = False

# create sidebar frame with widgets
self.sidebar_frame = CTkFrame(self, width=140, corner_radius=0)
self.logo_label = CTkLabel(self.sidebar_frame, text="CustomTkinter", font=CTkFont(size=20, weight="bold"))
self.theme_label = CTkLabel(self.sidebar_frame, text="Theme:", anchor="w")
self.theme_optionmenu = CTkOptionMenu(self.sidebar_frame, values=ThemeManager._built_in_themes,
command=self._change_theme_event)
self.theme_optionmenu.set(ThemeManager._currently_loaded_theme)
self.appearance_mode_label = CTkLabel(self.sidebar_frame, text="Appearance Mode:", anchor="w")
self.appearance_mode_optionemenu = CTkOptionMenu(self.sidebar_frame, values=["Light", "Dark", "System"],
command=self._change_appearance_mode_event)
self.appearance_mode_optionemenu.set(get_appearance_mode())
self.scaling_label = CTkLabel(self.sidebar_frame, text="UI Scaling:", anchor="w")
self.scaling_optionmenu = CTkOptionMenu(self.sidebar_frame, values=["80%", "90%", "100%", "110%", "120%"],
command=self._change_scaling_event)
widget_scaling = round(ScalingTracker.widget_scaling*100)
self.scaling_optionmenu.set(f"{widget_scaling}%")
self.drawing_label = CTkLabel(self.sidebar_frame, text="Drawing method:", anchor="w")
self.drawing_optionmenu = CTkOptionMenu(self.sidebar_frame, values=DrawEngine.DRAWING_METHODS,
command=self._change_drawing_event)
self.drawing_optionmenu.set(DrawEngine.preferred_drawing_method)

self.sidebar_frame.pack(side="left", fill="y")
self.logo_label.pack(side="top", fill="x", padx=5, pady=5)
self.theme_label.pack(side="top", fill="x", padx=20, pady=(20, 5))
self.theme_optionmenu.pack(side="top", fill="x", padx=20, pady=(0, 10))
self.appearance_mode_label.pack(side="top", fill="x", padx=20, pady=(20, 5))
self.appearance_mode_optionemenu.pack(side="top", fill="x", padx=20, pady=(0, 10))
self.scaling_label.pack(side="top", fill="x", padx=20, pady=(20, 5))
self.scaling_optionmenu.pack(side="top", fill="x", padx=20, pady=(0, 10))
self.drawing_label.pack(side="top", fill="x", padx=20, pady=(20, 5))
self.drawing_optionmenu.pack(side="top", fill="x", padx=20, pady=(0, 10))

# create main tabview
self.main_tabview = CTkTabview(self)

self.main_tabview.pack(side="left", fill="both", expand=True, padx=5, pady=(0, 5))

# buttons
self.buttons_frame = self.main_tabview.add("Buttons")

self.button_1 = CTkButton(self.buttons_frame)
self.button_2 = CTkButton(self.buttons_frame, hover=False, text="No Hover")
self.button_3 = CTkButton(self.buttons_frame, state="disabled", text="disabled")

self.button_1.pack(padx=20, pady=(self.SPACING, 5))
self.button_2.pack(padx=20, pady=(0, 5))
self.button_3.pack(padx=20, pady=(0, 5))

# choices
self.choices_frame = self.main_tabview.add("Choices")
self.combobox_1 = CTkComboBox(self.choices_frame,
values=["CTkComboBox", "Value 2", "Value 3", "User can also", "write any text"])
self.combobox_1.set("CTkComboBox")
self.combobox_2 = CTkComboBox(self.choices_frame, state="readonly",
values=["readonly", "Value 2", "Value 3", "User can only", "choose a value"])
self.combobox_2.set("readonly")
self.optionmenu = CTkOptionMenu(self.choices_frame, dynamic_resizing=False,
values=["CTkOptionMenu", "Value 2", "Value 3"])
self.seg_button1 = CTkSegmentedButton(self.choices_frame, values=["CTkSegmentedButton", "Value 2", "Value 3"])
self.seg_button1.set("CTkSegmentedButton")
self.seg_button2 = CTkSegmentedButton(self.choices_frame, values=["vertical", "Value 2", "Value 3"], orientation="vertical")
self.seg_button2.set("vertical")

self.combobox_1.pack(padx=20, pady=(self.SPACING, 5))
self.combobox_2.pack(padx=20, pady=(0, 5))
self.optionmenu.pack(padx=20, pady=(self.SPACING, 5))
self.seg_button1.pack(padx=20, pady=(self.SPACING, 5))
self.seg_button2.pack(padx=20, pady=(0, 5))

# text
self.text_frame = self.main_tabview.add("Text")
self.label = CTkLabel(self.text_frame, text="CTkLabel", height=1)
self.entry = CTkEntry(self.text_frame, placeholder_text="CTkEntry")
self.textbox = CTkTextbox(self.text_frame, width=320)
self.textbox.insert("0.0", "CTkTextbox\n\n" + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)

self.label.pack(padx=20, pady=(self.SPACING, 5))
self.entry.pack(padx=20, pady=(self.SPACING, 5))
self.textbox.pack(padx=20, pady=(self.SPACING, 5))

# boolean
self.boolean_frame = self.main_tabview.add("Boolean")
self.radio_var = IntVar(value=0)
self.radio_button_1 = CTkRadioButton(self.boolean_frame, variable=self.radio_var, value=0, width=130)
self.radio_button_2 = CTkRadioButton(self.boolean_frame, variable=self.radio_var, value=1, hover=False, text="No Hover", width=130)
self.radio_button_3 = CTkRadioButton(self.boolean_frame, variable=self.radio_var, value=2, state="disabled", text="Disabled", width=130)
self.checkbox_var = BooleanVar(value=True)
self.checkbox_1 = CTkCheckBox(self.boolean_frame, variable=self.checkbox_var, width=130)
self.checkbox_2 = CTkCheckBox(self.boolean_frame, hover=False, text="No Hover", width=130)
self.checkbox_3 = CTkCheckBox(self.boolean_frame, state="disabled", text="Disabled", width=130)
self.switch_var = BooleanVar(value=True)
self.switch_1 = CTkSwitch(self.boolean_frame, variable=self.switch_var, width=130)
self.switch_2 = CTkSwitch(self.boolean_frame, hover=False, text="No Hover", width=130)
self.switch_3 = CTkSwitch(self.boolean_frame, state="disabled", text="Disabled", width=130)

self.radio_button_1.pack(padx=20, pady=(self.SPACING, 5))
self.radio_button_2.pack(padx=20, pady=(0, 5))
self.radio_button_3.pack(padx=20, pady=(0, 5))
self.checkbox_1.pack(padx=20, pady=(self.SPACING, 5))
self.checkbox_2.pack(padx=20, pady=(0, 5))
self.checkbox_3.pack(padx=20, pady=(0, 5))
self.switch_1.pack(padx=20, pady=(self.SPACING, 5))
self.switch_2.pack(padx=20, pady=(0, 5))
self.switch_3.pack(padx=20, pady=(0, 5))

# bars
self.bars_frame = self.main_tabview.add("Bars")
self.label_progbar_1 = CTkLabel(self.bars_frame, text="CTkProgressBar - determinate", height=1)
self.progressbar_1 = CTkProgressBar(self.bars_frame, mode="determinate", determinate_speed=0.5)
self.label_progbar_2 = CTkLabel(self.bars_frame, text="CTkProgressBar - indeterminate", height=1)
self.progressbar_2 = CTkProgressBar(self.bars_frame, mode="indeterminate", indeterminate_speed=0.5)
self.label_slider_1 = CTkLabel(self.bars_frame, text="CTkSlider - with steps", height=1)
self.slider_1 = CTkSlider(self.bars_frame, from_=0, to=1, number_of_steps=4)
self.label_slider_2 = CTkLabel(self.bars_frame, text="CTkSlider - continuous", height=1)
self.slider_2 = CTkSlider(self.bars_frame, from_=10, to=100)
self.label_scrollbar_1 = CTkLabel(self.bars_frame, text="CTkScrollbar", height=1)
self.scrollbar_1 = CTkScrollbar(self.bars_frame, orientation="horizontal")
self.scrollbar_1.set(0, 0.3)

self.label_vertical = CTkLabel(self.bars_frame, text="vertical", height=1)
self.frame_vertical = CTkFrame(self.bars_frame)
self.progressbar_3 = CTkProgressBar(self.frame_vertical, orientation="vertical")
self.slider_3 = CTkSlider(self.frame_vertical, orientation="vertical")
self.scrollbar_2 = CTkScrollbar(self.frame_vertical, orientation="vertical")
self.scrollbar_2.set(0, 0.3)

self.progressbar_1.start()
self.progressbar_2.start()
self.slider_3.configure(command = self.progressbar_3.set)

self.label_progbar_1.pack(padx=20, pady=(self.SPACING, 5))
self.progressbar_1.pack(padx=20, pady=(0, 5))
self.label_progbar_2.pack(padx=20, pady=(0, 5))
self.progressbar_2.pack(padx=20, pady=(0, 5))
self.label_slider_1.pack(padx=20, pady=(self.SPACING, 5))
self.slider_1.pack(padx=20, pady=(0, 5))
self.label_slider_2.pack(padx=20, pady=(0, 5))
self.slider_2.pack(padx=20, pady=(0, 5))
self.label_scrollbar_1.pack(padx=20, pady=(self.SPACING, 5))
self.scrollbar_1.pack(padx=20, pady=(0, 5))

self.label_vertical.pack(padx=20, pady=(self.SPACING, 5))
self.frame_vertical.pack(padx=20, pady=(0, 5))
self.progressbar_3.pack(side="left", padx=20)
self.slider_3.pack(side="left", padx=20)
self.scrollbar_2.pack(side="left", padx=20)

# frames
self.frames_frame = self.main_tabview.add("Frames")
self.scrollable_frame = CTkScrollableFrame(self.frames_frame, label_text="CTkScrollableFrame",
fg_color=ThemeManager.theme["CTk"]["fg_color"])
self.tabview = CTkTabview(self.frames_frame, fg_color=ThemeManager.theme["CTk"]["fg_color"])
tab1 = self.tabview.add("CTkTabview")
tab2 = self.tabview.add("Tab 2")
tab3 = self.tabview.add("Tab 3")
CTkButton(tab1, text="Widget on 1st Tab").pack()
CTkCheckBox(tab2, text="Widget on 2nd Tab").pack()
CTkSwitch(tab3, text="Widget on 3rd Tab").pack()

for i in range(100):
switch = CTkSwitch(self.scrollable_frame, text=f"CTkSwitch {i+1}")
switch.pack(padx=20, pady=5)

self.scrollable_frame.pack(side=LEFT, padx=20, pady=(self.SPACING, 5))
self.tabview.pack(side=LEFT, padx=20, pady=(self.SPACING, 5))

# windows
self.windows_frame = self.main_tabview.add("Windows")
self.open_toplevel = CTkButton(self.windows_frame, text="Open CTkToplevel", command=self._open_ctktoplevel_event)
self.open_dialog = CTkButton(self.windows_frame, text="Open CTkInputDialog", command=self._open_input_dialog_event)

self.open_toplevel.pack(padx=20, pady=(self.SPACING, 5))
self.open_dialog.pack(padx=20, pady=(self.SPACING, 5))


def _open_ctktoplevel_event(self) -> None:
toplevel = CTkToplevel(self)
toplevel.geometry(f"{500}x{250}")
toplevel.resizable(True, True)
toplevel.title("CTkToplevel")

def _open_input_dialog_event(self) -> None:
dialog = CTkInputDialog(title="CTkInputDialog", text="Description of requested input")
dialog.get_input()

def _change_appearance_mode_event(self, new_appearance_mode: str) -> None:
set_appearance_mode(new_appearance_mode)

def _change_scaling_event(self, new_scaling: str) -> None:
new_scaling_float = int(new_scaling.replace("%", "")) / 100
set_widget_scaling(new_scaling_float)

def _change_theme_event(self, new_theme: str) -> None:
set_default_color_theme(new_theme)
self.new_instance_requested = True
self.destroy()

def _change_drawing_event(self, new_drawing_method: str) -> None:
DrawEngine.preferred_drawing_method = new_drawing_method
self.new_instance_requested = True
self.destroy()
8 changes: 5 additions & 3 deletions customtkinter/assets/themes/blue.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,25 @@
},
"CTkLabel": {
"corner_radius": 0,
"border_width": 0,
"fg_color": "transparent",
"border_color": ["#979DA2", "#565B5E"],
"text_color": ["gray10", "#DCE4EE"]
},
"CTkEntry": {
"corner_radius": 6,
"border_width": 2,
"fg_color": ["#F9F9FA", "#343638"],
"border_color": ["#979DA2", "#565B5E"],
"text_color":["gray10", "#DCE4EE"],
"text_color": ["gray10", "#DCE4EE"],
"placeholder_text_color": ["gray52", "gray62"]
},
"CTkCheckBox": {
"corner_radius": 6,
"border_width": 3,
"fg_color": ["#3B8ED0", "#1F6AA5"],
"border_color": ["#3E454A", "#949A9F"],
"hover_color": ["#3B8ED0", "#1F6AA5"],
"hover_color": ["#36719F", "#144870"],
"checkmark_color": ["#DCE4EE", "gray90"],
"text_color": ["gray10", "#DCE4EE"],
"text_color_disabled": ["gray60", "gray45"]
Expand Down Expand Up @@ -123,7 +125,7 @@
"border_width": 0,
"fg_color": ["#F9F9FA", "#1D1E1E"],
"border_color": ["#979DA2", "#565B5E"],
"text_color":["gray10", "#DCE4EE"],
"text_color": ["gray10", "#DCE4EE"],
"scrollbar_button_color": ["gray55", "gray41"],
"scrollbar_button_hover_color": ["gray40", "gray53"]
},
Expand Down
Loading