Windows#

App (also exported as ttk.Window) and ttk.Toplevel are enhanced replacements for tk.Tk / tk.Toplevel that fold window setup — size, position, icon, constraints — into the constructor, so a window is configured in one call instead of a scatter of geometry / minsize / wm_* calls afterward. Keep one App per program (the single-root rule — see Structuring an app); every other window is a Toplevel.

Windowing is also where the three platforms diverge most — DPI, transparency, maximize, and borderless windows all behave differently on Windows, macOS, and Linux. This guide flags those differences as it goes, and collects them in Cross-platform behavior at the end.

Creating the main window#

The one thing App needs is nothing — ttk.App() gives you a themed window. Everything else is a keyword you set once, up front:

import ttkbootstrap as ttk

app = ttk.App(
    title="Invoices",
    theme="nord-light",
    size=(800, 600),
    position=(200, 120),
    minsize=(480, 360),
)

app.mainloop()

Note

theme= is the canonical name; themename= is a permanent alias (the pre-2.0 spelling). Window is likewise a permanent alias for App — use whichever reads better. For choosing and switching themes, see Theming & Colors.

Window geometry#

A window’s size and location are one value in tkinter — the geometry string, "WIDTHxHEIGHT+X+Y" (e.g. "800x600+200+120"). The size and position constructor keywords build it for you from (width, height) and (x, y) pixel tuples; you can also read or set it directly with geometry():

app.geometry()                 # -> "800x600+200+120"
app.geometry("640x480")        # resize, leave position alone
app.geometry("+100+100")       # move, leave size alone

x and y are screen pixels from the top-left, and they are signed and edge-relative: a negative value measures from the opposite edge, so position=(-20, -20) pins the window near the bottom-right corner regardless of screen size.

Read the current size and location back with the winfo_* accessors — winfo_width() / winfo_height() / winfo_x() / winfo_y():

Warning

winfo_width() / winfo_height() report 1 until the window has been drawn. Call update_idletasks() first, or read winfo_reqwidth() / winfo_reqheight() (the requested size, valid immediately). See Widget & screen info.

Size constraints#

minsize and maxsize are (width, height) tuples that clamp how far the user can resize the window; resizable takes two booleans, one per axis, to lock resizing entirely:

app = ttk.App(minsize=(480, 360), resizable=(True, False))   # width flexes, height fixed

alpha sets whole-window transparency from 0.0 to 1.0 (opaque).

Note

Window transparency is immediate on Windows and macOS. On Linux it needs a compositing window manager to take effect, and ttkbootstrap applies it only once the window is first shown.

Window state#

A window is in one of a few states, read and set through state(): "normal", "iconic" (minimized), or "withdrawn" (hidden). The convenience methods are usually clearer — iconify() minimizes, deiconify() restores, withdraw() hides without a taskbar entry:

app.iconify()          # minimize
app.deiconify()        # restore
app.state()            # -> "normal" / "iconic" / "withdrawn"

Maximize and fullscreen are where platforms split:

app.state("zoomed")                     # maximize — Windows
app.attributes("-fullscreen", True)     # true fullscreen — Windows & Linux

Note

state("zoomed") maximizes on Windows; on Linux use attributes("-zoomed", True) (honored by most window managers); macOS has no programmatic maximize — the green traffic-light button toggles native fullscreen. attributes("-fullscreen", True) gives borderless fullscreen on Windows and Linux, and drives native fullscreen on macOS. Provide an Escape binding to leave fullscreen — there is no titlebar to click.

Second windows — Toplevel#

Every window after the first is a ttk.Toplevel. It shares App’s constructor conveniences (size, position, iconphoto, …) and inherits the app’s theme and icon automatically.

Warning

Toplevel’s first positional argument is the title, not the parent — ttk.Toplevel(app) sets the title to the app object. Pass the parent as master=:

win = ttk.Toplevel(master=app, title="Details", size=(400, 300))

Toplevel adds a few window-manager options App doesn’t need. Several are platform-specific — they are honored where they apply and ignored elsewhere, so they are safe to pass on any OS:

  • topmost=True keeps the window above all others (all platforms).

  • tool_window=True gives a thin tool-window frame and hides it from the taskbar — Windows only.

  • window_type=... requests a window kind — "splash", "utility", "tooltip" — for borderless/auxiliary windows. On macOS these map to native window styles; on Linux it is a hint to the window manager; on Windows it has no effect (use tool_window / override_redirect).

  • iconify=True starts the window minimized.

  • transient=parent marks it a satellite of parent (see below).

Note

override_redirect=True strips all window-manager decoration (no titlebar, border, or controls) — the basis of a splash screen. It has no effect on macOS, where enabling it breaks event handling, so ttkbootstrap ignores it there; for borderless popups on macOS use window_type instead.

Positioning#

place_window_center() centers a window on screen — on the monitor under the mouse cursor when the optional screeninfo package is installed, and clamped so the window stays fully visible either way. Call it after the window’s size is known (the constructor size counts):

win = ttk.Toplevel(master=app, title="About", size=(360, 200))
win.place_window_center()

Note

Without screeninfo installed, centering falls back to the primary screen, so on a multi-monitor setup the window may land on the main display rather than the active one. screeninfo is an optional dependency — ttkbootstrap’s only required one is Pillow.

A small About Toplevel window centered with place_window_center — light theme A small About Toplevel window centered with place_window_center — dark theme

Focus, modality & lifecycle#

Beyond its size and position, a window has three behaviors you control in code: which window has the keyboard focus, whether it is modal (blocks the rest of the app), and its lifecycle — mapping, closing, and cleanup. These apply to any window.

Focus#

The focused widget receives keyboard input. Tk moves focus as the user tabs and clicks; to direct it yourself, call focus_set() on a widget once its window is mapped:

entry.focus_set()          # put the cursor in this entry

focus_get() returns the widget that currently has focus (or None). focus_force() steals focus even from other applications — it is disruptive, so avoid it unless you truly must.

Stacking and satellites#

lift() raises a window above its siblings; lower() drops it behind them. Marking a window transient to a parent makes it a satellite: it stays above the parent, minimizes with it, and is kept off the taskbar — the right setting for dialogs and inspectors. Set it in the constructor or later:

win = ttk.Toplevel(master=app, title="Inspector", transient=app)
# or later:  win.transient(app)

Modality#

A modal window blocks interaction with the rest of the app until it closes. Two calls make it so:

  • grab_set() routes all input to the window — a local grab, confined to your application. (Avoid grab_set_global(), which grabs the entire screen.) grab_release() ends it, and grab_status() reports "local", "global", or None.

  • parent.wait_window(win) blocks the calling code until win is destroyed.

Because wait_window returns only after the window closes, the window can leave a result behind for the caller to read — that is how a dialog “returns a value.” The Open a second window recipe puts these together into a reusable modal dialog, and the Dialogs guide covers the shipped modal dialogs built on the same mechanism.

Lifecycle#

destroy() closes a window for good and tears down its child widgets. Cancel any after timers or variable traces first, or they fire against dead widgets.

Clicking the window’s close button () does not call destroy() directly — it asks the window to close. Handle that with on_close (on App and every Toplevel): your callback runs, then the window is destroyed for you. Return False to cancel the close and keep the window open:

def confirm_close():
    if not user_confirms():
        return False       # stay open
    # otherwise the window closes — no destroy() call needed

win.on_close(confirm_close)

Pass the same callable as App(on_close=...) / Toplevel(on_close=...) to set it at construction. on_close handles the close button on Windows, macOS, and Linux; on macOS, the application menu’s Quit (⌘Q) is a separate, app-wide action — wire it with ttkbootstrap.Menu.on_quit().

Application icon#

iconphoto= sets the titlebar/taskbar icon from an image path; the default is ttkbootstrap’s brand icon, and iconphoto=None leaves the platform default. The format that works best differs by platform — a .ico file on Windows, a PNG elsewhere — and ttkbootstrap picks the right mechanism for you. A Toplevel inherits the application icon automatically. See Show images and icons for building icons from your own art.

Light & dark#

A window follows the active theme, and its light/dark mode is switched through the same App: app.theme_mode reads "light" / "dark", app.toggle_theme() flips between the family’s pair, and App(light_theme=..., dark_theme=...) designates which two to toggle. The Theming & Colors guide covers this in full.

High-DPI displays#

On a high-resolution display, a DPI-unaware app is scaled up by the OS and looks blurry, or renders everything tiny. Each platform handles this differently, and App does the right thing by default:

  • WindowsApp enables DPI awareness automatically (high_dpi=True), so text and widgets stay crisp. It must happen before the root is created, which the constructor handles; call enable_high_dpi_awareness() yourself only if you are not using App.

  • macOS — HiDPI (“Retina”) is handled natively by the OS; nothing to do.

  • Linux — there is no automatic scaling. Pass a factor after creating the root to scale the whole UI: enable_high_dpi_awareness(app, 1.75) (1.62.0 is typical for a 4K panel).

For code that sets pixel geometry or builds image assets, convert logical sizes to physical pixels for the current display with scale_size:

ttk.scale_size(app, 24)          # 24 logical px -> physical px for this display
ttk.scale_size(app, [24, 24])    # a (width, height) pair -> scaled ints

Cross-platform behavior#

Windowing is the corner of tkinter where “write once, run anywhere” leaks the most. The differences below are handled or guarded by App/Toplevel where possible, but they affect what you can rely on:

Feature

Windows

macOS

Linux (X11)

High-DPI

auto (high_dpi=True)

native (Retina)

manual scaling

Transparency (alpha)

immediate

immediate

needs a compositor

Maximize

state("zoomed")

none (native fullscreen)

attributes("-zoomed", …)

tool_window

window_type (borderless)

— (use override_redirect)

native styles

WM hint

override_redirect

ignored (breaks events)

Taskbar grouping

by app id (automatic)

Dock (native)

WM-dependent

Sidebar — the deferred-config seam

A few settings need to apply before any widget is built but read most naturally at the top of the file, before App() exists. set_locale, set_global_family, and set_default_button queue when called pre-root and flush the moment the root comes up (they apply live if a root already exists), so this works as written:

import ttkbootstrap as ttk

ttk.set_global_family("Segoe UI")     # queued...
ttk.set_default_button("primary")

app = ttk.App()                        # ...applied here

See also