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:

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:
| Part | Widget | pack() options |
|---|---|---|
| Header | Label | fill="x", ipady=8 |
| Button bar | Frame + 2 Buttons | side="bottom", side="right" |
| Bike list | Frame + Labels | side="left", fill="y", anchor="w" |
| Price area | Frame + Labels | fill="both", expand=True |
| Book now | Button | ipadx=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.

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")

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)

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)

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)

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

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:

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: