A question about styling a CustomTkinter combobox

Hi all,

Hope it’s ok to ask here, if not please move my post to the right section.

I just started using CustomTkinter and would like to style my widgets.
I’m looking for a way to change/remove this ugly white box around the dropdown of the combobox:

In my custom class (see below) I have used about every property a combobox has, but I can’t get rid of this thick white border. On an (outdated) wiki page it looks like I want it to look:

How can I fix this?

Thanks!

My class:

clr_bgr = '#202020'             # Background
clr_grn = '#009900'             # Green
clr_hvr = '#303030'             # Hover color


class ComboboxCst(ctk.CTkComboBox):

    def __init__(self, master, values, command=None, 
                 width=150, height=20, corner_radius=5,
                 border_width=1, border_color=clr_grn,
                 font=('Arial', 10), text_color=clr_grn, 
                 dropdown_font=('Arial', 10),
                 dropdown_text_color=clr_grn, 
                 fg_color=clr_bgr, dropdown_fg_color=clr_bgr,
                 dropdown_hover_color=clr_hvr, 
                 button_color=clr_hvr, 
                 button_hover_color=clr_hvr,
                 hover=True, justify='left'):

        super().__init__(master, values=values, 
                         command=command, 
                         width=width, height=height,
                         corner_radius=corner_radius, 
                         border_width=border_width,
                         border_color=border_color, 
                         font=font, text_color=text_color,
                         dropdown_font=dropdown_font, 
                         dropdown_text_color=dropdown_text_color,
                         fg_color=fg_color, 
                         dropdown_fg_color=dropdown_fg_color,
                         dropdown_hover_color=dropdown_hover_color,
                         button_color=button_color, 
                         button_hover_color=button_hover_color,
                         hover=hover, justify=justify)

Hello Jordi,

Let me know if you found somehting about this topic. i was trying to do something about this too but nothing seem to work. I recreated this as a label-button which creates a pop up window right beneath but it looks terrible jaja

Hello Jordi,

Fun enough, i just managed to come up with something nice. Please try out my class:

import customtkinter as ctk


class CTkDropdown(ctk.CTkFrame):
    """
    Reusable dropdown widget for CustomTkinter.

    Usage:
        from ctk_dropdown import CTkDropdown
        combo = CTkDropdown(root, values=["A", "B", "C"])
        combo.pack()
    """

    def __init__(
        self,
        master,
        values=None,
        width=130,
        height=35,
        corner_radius=10,
        fg_color=None,
        dropdown_color=None,
        hover_color=None,
        button_color=None,
        button_hover_color=None,
        selected_color=None,
        text_color=None,
        font=None,
        command=None,
        **kwargs,
    ):
        super().__init__(
            master,
            width=width,
            height=height,
            corner_radius=corner_radius,
            fg_color=fg_color,
            **kwargs,
        )

        self.grid_propagate(False)
        self.pack_propagate(False)

        self.values = values or []
        self.command = command
        self._current_value = self.values[0] if self.values else ""

        self.width = width
        self.height = height
        self.corner_radius = corner_radius

        self.dropdown_color = dropdown_color or "#565656"
        self.hover_color = hover_color or "#444444"
        self.selected_color = selected_color or "#383838"
        self.button_color = button_color or "transparent"
        self.button_hover_color = button_hover_color or "#4A4A4A"
        self.text_color = text_color or "white"
        self.font = font or ctk.CTkFont(size=10)

        self.popup = None
        self.popup_frame = None
        self.is_open = False
        self._highlight_index = 0
        self._outside_click_binding = None
        self._configure_binding = None

        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=0)

        self.label = ctk.CTkLabel(
            self,
            text=self._current_value,
            anchor="w",
            padx=12,
            text_color=self.text_color,
            font=self.font,
        )
        self.label.grid(row=0, column=0, sticky="nsew", padx=(10, 0), pady=4)

        self.arrow_button = ctk.CTkButton(
            self,
            text="▼",
            width=34,
            fg_color=self.button_color,
            hover_color=self.button_hover_color,
            corner_radius=8,
            command=self.toggle,
        )
        self.arrow_button.grid(row=0, column=1, sticky="ns", padx=(0, 5), pady=4)

        self.bind("<Button-1>", lambda e: self.toggle())
        self.label.bind("<Button-1>", lambda e: self.toggle())

    def get(self):
        return self._current_value

    def set(self, value):
        if value not in self.values:
            return
        self._current_value = value
        self.label.configure(text=value)

    def configure_values(self, values):
        self.values = list(values)
        if self.values:
            self.set(self.values[0])

    def toggle(self):
        if self.is_open:
            self.close()
        else:
            self.open()

    def open(self):
        if self.is_open:
            return
        self.is_open = True
        self.arrow_button.configure(text="▲")
        self._build_popup()

        root = self.winfo_toplevel()
        self._outside_click_binding = root.bind("<Button-1>", self._on_global_click, add="+")
        self._configure_binding = root.bind("<Configure>", lambda e: self._reposition_popup(), add="+")

    def close(self):
        if not self.is_open:
            return
        self.is_open = False
        self.arrow_button.configure(text="▼")

        root = self.winfo_toplevel()
        if self._outside_click_binding is not None:
            try:
                root.unbind("<Button-1>", self._outside_click_binding)
            except Exception:
                pass
        if self._configure_binding is not None:
            try:
                root.unbind("<Configure>", self._configure_binding)
            except Exception:
                pass

        self._outside_click_binding = None
        self._configure_binding = None

        if self.popup is not None:
            try:
                self.popup.destroy()
            except Exception:
                pass
        self.popup = None
        self.popup_frame = None

    def _select(self, value):
        self.set(value)
        self.close()
        if self.command is not None:
            self.command(value)

    def _build_popup(self):
        if self.popup is not None:
            self.popup.destroy()

        self.popup = ctk.CTkToplevel(self)
        self.popup.overrideredirect(True)
        self.popup.attributes("-topmost", True)

        self.update_idletasks()

        x = self.winfo_rootx()
        y = self.winfo_rooty() + self.winfo_height()
        popup_height = min(220, len(self.values) * 36 + 10)
        self.popup.geometry(f"{self.width}x{popup_height}+{x}+{y}")

        self.popup_frame = ctk.CTkScrollableFrame(
            self.popup,
            fg_color=self.dropdown_color,
            corner_radius=self.corner_radius,
        )
        self.popup_frame.pack(fill="both", expand=True)

        self.option_buttons = []
        for i, value in enumerate(self.values):
            colour = self.selected_color if value == self._current_value else "transparent"
            btn = ctk.CTkButton(
                self.popup_frame,
                text=value,
                anchor="w",
                fg_color=colour,
                hover_color=self.hover_color,
                text_color=self.text_color,
                font=self.font,
                corner_radius=6,
                command=lambda v=value: self._select(v),
            )
            btn.pack(fill="x", padx=6, pady=2)
            self.option_buttons.append(btn)
            if value == self._current_value:
                self._highlight_index = i

    def _reposition_popup(self):
        if not self.is_open or self.popup is None:
            return
        self.update_idletasks()
        x = self.winfo_rootx()
        y = self.winfo_rooty() + self.winfo_height()
        self.popup.geometry(f"+{x}+{y}")

    def _on_global_click(self, event):
        if not self.is_open or self.popup is None:
            return

        px = self.popup.winfo_rootx()
        py = self.popup.winfo_rooty()
        pw = self.popup.winfo_width()
        ph = self.popup.winfo_height()

        x = event.x_root
        y = event.y_root

        inside_popup = px <= x <= px + pw and py <= y <= py + ph
        wx = self.winfo_rootx()
        wy = self.winfo_rooty()
        ww = self.winfo_width()
        wh = self.winfo_height()
        inside_widget = wx <= x <= wx + ww and wy <= y <= wy + wh

        if not inside_popup and not inside_widget:
            self.close()


CTkComboBox = CTkDropdown

__all__ = ["CTkDropdown", "CTkComboBox"]