Skip to content

Support for Custom Title Bar in Tkinter to Control Minimize/Maximize Buttons Cross-Platform #135024

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
DevTwilight opened this issue Jun 2, 2025 · 4 comments
Labels
pending The issue will be closed if no feedback is provided stdlib Python modules in the Lib dir topic-tkinter type-feature A feature request or enhancement

Comments

@DevTwilight
Copy link

DevTwilight commented Jun 2, 2025

Feature or enhancement

Proposal:

Summary

Tkinter lacks a native, cross-platform way to enable, disable, or customize the minimize and maximize buttons on the window’s title bar. Platform-specific hacks (e.g., ctypes on Windows, overrideredirect(True) on macOS/Linux) either break other window features or aren’t consistent.

A common modern approach in desktop applications is to create a custom title bar — allowing full control over minimize, maximize, and close buttons and the window frame, while keeping a consistent look and feel across platforms.

This feature request proposes official support or easy tooling within Tkinter to create custom title bars that replicate window controls and support overriding minimize/maximize behavior in a cross-platform manner.


Motivation

  • Native window controls in Tkinter are limited and platform-dependent.
  • overrideredirect(True) removes the entire window frame, disabling resizing, dragging, and native controls.
  • Developers want to create polished, controlled UI experiences without dealing with fragile hacks.
  • Custom title bars are popular in modern apps for branding and precise control.
  • Tkinter users currently must manually re-implement window dragging, buttons, and state management.

Current Situation

  • No built-in Tkinter API to customize the title bar.
  • To disable maximize/minimize buttons, developers rely on platform-specific hacks.
  • Using overrideredirect(True) removes all decorations, forcing manual implementation of window movement, resizing, and buttons.
  • This is complex and error-prone.

Proposed Solution

  • Provide a standardized, cross-platform Tkinter API or widget to implement custom title bars.
  • Include built-in support for window dragging, minimize, maximize, and close actions.
  • Allow toggling of minimize/maximize buttons easily.
  • Support native and custom-drawn title bars interchangeably.
  • Make it easy to style and theme to fit app branding.

Example conceptual API:

root = tk.Tk()
titlebar = CustomTitleBar(root,
                         minimize=True,
                         maximize=False,
                         close=True,
                         on_minimize=some_callback,
                         on_maximize=some_callback,
                         on_close=root.destroy)
titlebar.pack(fill='x')

Benefits

  • Full control over window controls, consistent UI on all platforms.
  • No reliance on fragile platform-specific hacks.
  • Enables more polished and professional-looking Tkinter apps.
  • Helps build kiosk or restricted UI modes easily.
  • Simplifies handling of window state changes.

Additional Context

  • Custom title bars are common in frameworks like Electron, Qt, and others.
  • Tkinter’s limitations in window chrome handling have long been a challenge.
  • This could be implemented as a new tkinter widget or extension package.
  • Will require handling platform-specific window state commands internally.

Request

  • Consider adding a native or official way to support custom title bars in Tkinter.
  • Alternatively, provide a documented recommended pattern for implementing custom title bars.
  • Documentation and example code would be very helpful.

Thank you for your consideration! I’d be happy to help prototype or test this feature.


@DevTwilight DevTwilight added the type-feature A feature request or enhancement label Jun 2, 2025
@picnixz picnixz added stdlib Python modules in the Lib dir topic-tkinter labels Jun 2, 2025
@terryjreedy terryjreedy added the pending The issue will be closed if no feedback is provided label Jun 2, 2025
@terryjreedy
Copy link
Member

I believe there has been an IDLE issue, which I cannot find, about title bar control, in particular, min/max buttons. I haven't gotten to it yet as other issue are higher priority. From what you say, this would be a helpful new feature. That said, the new feature template says "Major feature proposals should generally be discussed on Discourse before opening a GitHub issue." This is a major new feature for the purpose of that request, and a post there will be seen by more tkinter users. This should be closed in the meanwhile.

If you can implement a prototype just in Python code, it would be good to release it on pypi.org so it can be tested.

@DevTwilight
Copy link
Author

Thank you for the response!

I wanted to clarify the core issue I've encountered: when using overrideredirect(True) to remove the native title bar in Tkinter, it indeed removes the default window chrome, but when implementing a custom title bar with minimize, maximize, and close buttons, things start to break down—particularly with minimize. Trying to minimize the window often crashes the app or leads to unexpected behavior.

There are workarounds, but they are quite complex and typically involve diving into platform-specific APIs like Windows’ ctypes or win32gui. Not only are these solutions fragile and error-prone, but they're also tied to Windows only, leaving Linux and macOS out. This undermines Tkinter's goal of being a cross-platform GUI toolkit.

That's why I believe this should be addressed directly within Tkinter itself, not as a third-party module or external workaround. A built-in, cross-platform way to customize or recreate the window frame—while preserving window functionality like minimize/maximize/close—is important for modern UI development. It empowers developers to build polished, branded, and consistent experiences across all platforms without resorting to low-level hacks.

Thanks again for considering this!

@terryjreedy
Copy link
Member

'tkinter' == '(tcl/)tk inter(face)' For widgets, the interface is a fairly thin Python class interface wrapping the tk functions. If you look at tkinter.__init__, nearly all widget methods can a function of the tck interpreter object. _tkinter.c has the code for starting and interacting with the appropriate OS version of the tcl interpreter and for conversions between Python object and tcl strings. It seems that you are asking for a new ttk widget in tk itself.

@DevTwilight
Copy link
Author

Custom Title Bar Prototype for Tkinter

This is a working prototype demonstrating how to implement a fully custom title bar and window border in Tkinter, replacing the native window frame with custom controls for minimize, maximize, and close.

import tkinter as tk

FireRed = "#CC3300"

class CustomWindow(tk.Tk):
    def __init__(self):
        super().__init__()

        self.overrideredirect(True)
        self.geometry("900x600+100+100")

        border_thickness = 4
        self.border_frame = tk.Frame(self, bg=FireRed)
        self.border_frame.pack(fill="both", expand=True, padx=0, pady=0)

        self.inner_frame = tk.Frame(self.border_frame, bg="white")
        self.inner_frame.pack(padx=border_thickness, pady=border_thickness, fill="both", expand=True)

        # Title bar with buttons at the top
        title_bar_height = 40
        self.title_bar = tk.Frame(self.inner_frame, bg="white", height=title_bar_height)
        self.title_bar.pack(fill="x", side="top")

        # Buttons frame top right with some vertical padding
        self.button_frame = tk.Frame(self.title_bar, bg="white")
        self.button_frame.pack(side="right", padx=10, pady=(8, 8))

        btn_style = dict(width=3, font=("Segoe UI", 14), bd=0, relief="flat", padx=0, pady=0)

        self.min_button = tk.Button(self.button_frame, text="—", command=self.minimize_window, **btn_style)
        self.max_button = tk.Button(self.button_frame, text="□", command=self.maximize_restore_window, **btn_style)
        self.close_button = tk.Button(self.button_frame, text="✕", command=self.close_window, **btn_style)

        self.min_button.pack(side="left", padx=(0, 5))
        self.max_button.pack(side="left", padx=(0, 5))
        self.close_button.pack(side="left")

        # Hover effects
        self.min_button.bind("<Enter>", lambda e: self.min_button.config(bg="yellow"))
        self.min_button.bind("<Leave>", lambda e: self.min_button.config(bg="white"))

        self.max_button.bind("<Enter>", lambda e: self.max_button.config(bg="yellow"))
        self.max_button.bind("<Leave>", lambda e: self.max_button.config(bg="white"))

        self.close_button.bind("<Enter>", lambda e: self.close_button.config(bg="red", fg="white"))
        self.close_button.bind("<Leave>", lambda e: self.close_button.config(bg="white", fg="black"))

        # Drag window by title bar
        self.title_bar.bind("<ButtonPress-1>", self.start_move)
        self.title_bar.bind("<B1-Motion>", self.do_move)

        # CENTER LABEL in window (below title bar)
        self.center_label = tk.Label(self.inner_frame,
                                     text="Custom titlebar prototype",
                                     font=("Segoe UI", 24, "bold"),
                                     bg="white",
                                     fg="black")
        self.center_label.pack(expand=True)

        self.is_maximized = False
        self.normal_geometry = self.geometry()

    def start_move(self, event):
        self.x = event.x
        self.y = event.y

    def do_move(self, event):
        x = event.x_root - self.x
        y = event.y_root - self.y
        self.geometry(f"+{x}+{y}")

    def minimize_window(self):
        self.update_idletasks()
        self.overrideredirect(False)
        self.iconify()
        def restore_override():
            if not self.state() == 'iconic':
                self.overrideredirect(True)
            else:
                self.after(100, restore_override)
        self.after(100, restore_override)

    def maximize_restore_window(self):
        if not self.is_maximized:
            self.normal_geometry = self.geometry()
            self.geometry(f"{self.winfo_screenwidth()}x{self.winfo_screenheight()}+0+0")
            self.is_maximized = True
            self.max_button.config(text="❐")  # Restore icon
        else:
            self.geometry(self.normal_geometry)
            self.is_maximized = False
            self.max_button.config(text="□")

    def close_window(self):
        self.destroy()

if __name__ == "__main__":
    app = CustomWindow()
    app.mainloop()

Code Explanation

  • overrideredirect(True) disables the native window frame, allowing a fully custom title bar and border.
  • The outer border_frame draws a solid FireRed border (thickness 4 px) around the window.
  • The inner frame holds the white background content area.
  • The title_bar frame contains the minimize, maximize/restore, and close buttons aligned top-right with some padding.
  • Buttons change color on hover: yellow for minimize/maximize, red for close.
  • The title bar supports dragging the window by tracking mouse movements.
  • Minimizing uses a workaround: disables overrideredirect, calls iconify(), then re-enables overrideredirect after restore to avoid errors on Windows.
  • Maximizing toggles between fullscreen and original window size.
  • Close button destroys the window.
  • A centered label below the title bar shows the window content.

Limitations and Behavior Across Platforms

  • Windows:
    Works smoothly with the minimize workaround. The border, custom buttons, drag, and maximize behave as expected.

  • Linux/macOS:
    Behavior can vary by window manager and Tkinter/Tk implementation.

    • Minimizing may fail or cause errors due to overrideredirect restrictions.
    • Maximizing and dragging usually work but can be inconsistent.
    • The custom border appearance depends on platform window manager.
      Full consistency would require platform-specific adjustments or external libraries.

Summary

This prototype confirms it’s possible to implement a custom title bar and window frame in pure Tkinter, with native window functions recreated as custom widgets.

However, platform differences and Tkinter limitations mean this is a solid base but not a full replacement for native window frames without further work.


Below is a screenshot showcasing the final prototype in action:

Image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pending The issue will be closed if no feedback is provided stdlib Python modules in the Lib dir topic-tkinter type-feature A feature request or enhancement
Projects
None yet
Development

No branches or pull requests

3 participants