Tkinter pack() Geometry Manager in Python

A bike rental shop asked me for a booking window with a title bar, a bike list, a price area and two buttons. Tkinter pack handles all of it: this geometry manager stacks each widget against one side of its parent.

Here’s what we’re building, running for real on Windows 11:

Finished Tkinter pack layout: a blue Trailhead Bike Rentals header, a light blue bike list on the left, a white price area and Cancel and Book now buttons at the bottom right
The finished booking window, laid out with pack() only.

In this tutorial, I’ll build that window one pack() option at a time on Python 3.12.5 with Tk 8.6. The short version: side picks the edge, fill stretches the widget and expand hands it the leftover space.

header.pack(fill="x")                       # full-width bar
sidebar.pack(side="left", fill="y")         # left column
main.pack(side="left", fill="both", expand=True)  # takes the rest

Before You Start: what you need for a Tkinter pack layout

Tkinter ships with the python.org installers, so there’s nothing to install. If you’ve never opened a Tkinter window, my Python GUI programming guide covers the basics first.

Here are the window’s five parts and their pack() options:

PartWidgetpack() options
HeaderLabelfill="x", ipady=8
Button barFrame + 2 Buttonsside="bottom", side="right"
Bike listFrame + Labelsside="left", fill="y", anchor="w"
Price areaFrame + Labelsfill="both", expand=True
Book nowButtonipadx=14

The sample rental is an E-Bike for 4 hours at $18.50 per hour, so the total is $74.00. You’ll save every step in one file, bike_rentals.py.

Step 1: Create the window and pack the header

You start with one widget on screen. Create the root window, add a header Label and call pack() with no options:

import tkinter as tk

root = tk.Tk()
root.title("Trailhead Bike Rentals")
root.geometry("560x340")

header = tk.Label(root, text="Trailhead Bike Rentals",
                  bg="#1e3a8a", fg="white",
                  font=("Segoe UI", 14, "bold"))
header.pack()

root.mainloop()

Run it with python bike_rentals.py. Labels take bg, fg and font, as in my Tkinter Label tutorial.

Tkinter window with a dark blue Trailhead Bike Rentals label centered at the top, only as wide as its text, boxed in red
With no options, pack() puts the label at the top center, sized to its text.

Checkpoint: header.pack_info() reports side top, fill none, expand 0 and anchor center. Those are the defaults.

Step 2: Stretch the header across the window with fill=”x”

You want the bar to run edge to edge, so change the last line of Step 1 to this:

header.pack(fill="x")
Tkinter window where the dark blue header label now spans the full width of the window, boxed in red
fill=”x” stretches the header across the full width.

Checkpoint: the header’s width now matches the window’s width.

fill takes "x", "y", "both" or "none". It stretches a widget inside its strip but never widens the strip. The window size itself comes from geometry(), explained in my Tkinter window size guide.

Step 3: Add a button bar with side=”bottom” and side=”right”

Next, the Cancel and Book now buttons go in a gray bar along the bottom. Add this below the header:

footer = tk.Frame(root, bg="#e5e7eb")
footer.pack(side="bottom", fill="x")

book = tk.Button(footer, text="Book now")
book.pack(side="right", padx=8, pady=8)
cancel = tk.Button(footer, text="Cancel")
cancel.pack(side="right", pady=8)
Tkinter window with a gray button bar along the bottom edge holding Cancel and Book now buttons on the right, boxed in red
The footer sits on the bottom edge; both buttons hug its right side.

Book now is packed first, so it takes the far right. Neither button has a command yet; my Python Tkinter Button guide shows how to wire one up.

Important: pack handles widgets in the order you pack them. Each claims a full strip along its side of the free space (Tk calls it the cavity), so the footer goes in before the sidebar to get the full width.

Checkpoint: the footer touches the bottom of the window and Book now sits to the right of Cancel.

Step 4: Add a sidebar with side=”left” and fill=”y”

The bike list is a column on the left. Add a Frame with its own labels:

sidebar = tk.Frame(root, bg="#dbeafe")
sidebar.pack(side="left", fill="y")

tk.Label(sidebar, text="Choose a bike", bg="#dbeafe",
         font=("Segoe UI", 10, "bold")).pack(anchor="w", padx=12,
                                              pady=(10, 4))
for bike in ("City Cruiser", "Mountain Bike", "E-Bike"):
    tk.Label(sidebar, text=bike, bg="#dbeafe").pack(anchor="w",
                                                    padx=12)
Tkinter window with a light blue sidebar on the left listing City Cruiser, Mountain Bike and E-Bike between the header and the button bar, boxed in red
The sidebar fills the height between the header and the footer.

fill="y" stretches the frame from the header down to the footer, and anchor="w" lines the labels up on the left (west) edge. A Frame is just a container, as my Python Tkinter Frame tutorial explains.

Checkpoint: the sidebar stops at the button bar, because the footer was packed first.

Step 5: Let the main area grow with expand=True

The price area should take all the space that’s left. Add the main frame:

main = tk.Frame(root, bg="white")
main.pack(side="left", fill="both", expand=True, padx=10, pady=10)

for line in ("E-Bike, 4 hours", "Rate: $18.50 per hour",
             "Total: $74.00"):
    tk.Label(main, text=line, bg="white",
             font=("Segoe UI", 11)).pack(anchor="w", padx=12, pady=2)
Before and after: the white Tkinter price frame sized to its labels with plain pack(side=left), then filling the free space after fill=both and expand=True
Left: side=”left” alone. Right: the same frame with fill=”both” and expand=True.

expand=True gives the widget a share of any extra space in the parent, and fill="both" stretches it to cover that share. With only one of them, you get extra empty room or a widget that can’t grow.

Checkpoint: the white area fills the space right of the sidebar, inset by the 10-pixel padx and pady.

Step 6: Add Tkinter pack padding with padx, pady, ipadx and ipady

padx and pady add space outside a widget; ipadx and ipady add it inside, so the widget itself gets bigger. Change the header and Book now lines to:

header.pack(fill="x", ipady=8)                      # taller bar
book.pack(side="right", padx=8, pady=8, ipadx=14)   # wider button
Tkinter window with a taller blue header and a wider Book now button than Cancel, both boxed in red
Internal padding makes the header taller and Book now wider.

Checkpoint: Book now is now wider than Cancel, and the header bar is taller.

Test the pack layout by resizing the window

A good layout survives a resize, so you should always test one. I set the window to 820 x 480 from code:

Resized Tkinter bike rental window where the header and footer stretch to the new width, the sidebar keeps its width and the white main area grows, boxed in red
After the resize, only the main area changes size in both directions.

The header and footer stretched, the sidebar kept its width and grew taller, and the price area took all the new room, because it’s the only widget with expand=True.

Hide and show a widget with pack_forget() and before=

Same window, now with a way to hide the bike list. pack_forget() takes a widget off the screen without destroying it. But watch what happens when you pack it again the obvious way: