How to Use Tkinter Pack Fill and Expand in Python
Creating responsive GUI layouts that adapt gracefully when users resize windows is essential for professional applications. Tkinter's pack() geometry manager provides two key parameters, fill and expand, that control how widgets claim and occupy available space. Understanding the distinction between these parameters transforms static interfaces into dynamic, polished applications.
Understanding the Concepts
Think of each widget as having two components: its allocated space (parcel) and its visible presence within that space:
expand: Determines whether the widget's parcel grows to claim unused space in the parent containerfill: Determines whether the widget stretches to occupy its allocated parcel
| Parameter | Purpose | Values |
|---|---|---|
expand | Claim available space | True or False |
fill | Stretch to fill parcel | 'x', 'y', 'both', 'none' |
Visual Demonstration
Run this example and resize the window to observe how each configuration behaves:
import tkinter as tk
root = tk.Tk()
root.title("Pack Fill and Expand Demo")
root.geometry("400x300")
# Default: Fixed size, positioned at top
tk.Label(root, text="Default (no options)", bg="#e74c3c",
fg="white", pady=10).pack()
# Fill X: Stretches horizontally, no vertical expansion
tk.Label(root, text="fill='x'", bg="#3498db",
fg="white", pady=10).pack(fill='x')
# Expand only: Claims vertical space but doesn't stretch
tk.Label(root, text="expand=True (no fill)", bg="#2ecc71",
pady=10).pack(expand=True)
# Expand and Fill: Claims space AND stretches to fill it
tk.Label(root, text="expand=True, fill='both'", bg="#9b59b6",
fg="white", pady=10).pack(expand=True, fill='both')
root.mainloop()
When you resize the window vertically, notice that the green label (expand only) moves to stay centered in its claimed space, while the purple label (expand + fill) actually grows larger.
Fill Options Explained
import tkinter as tk
root = tk.Tk()
root.geometry("300x200")
# fill='x' - Stretch horizontally only
tk.Label(root, text="fill='x'", bg="coral").pack(fill='x', pady=5)
# fill='y' - Stretch vertically (needs expand or side='left')
tk.Label(root, text="fill='y'", bg="lightblue").pack(fill='y', side='left', padx=5)
# fill='both' - Stretch in both directions
tk.Label(root, text="fill='both'", bg="lightgreen").pack(fill='both', expand=True)
root.mainloop()
Combining with Side Parameter
The side parameter determines stacking direction, affecting how fill and expand work:
import tkinter as tk
root = tk.Tk()
root.geometry("400x300")
# Header: Full width at top
header = tk.Frame(root, bg="#2c3e50", height=50)
header.pack(side='top', fill='x')
tk.Label(header, text="Header", bg="#2c3e50", fg="white").pack()
# Sidebar: Full height on left
sidebar = tk.Frame(root, bg="#34495e", width=100)
sidebar.pack(side='left', fill='y')
tk.Label(sidebar, text="Sidebar", bg="#34495e", fg="white").pack(pady=20)
# Main content: Takes remaining space
main = tk.Frame(root, bg="#ecf0f1")
main.pack(side='left', expand=True, fill='both')
tk.Label(main, text="Main Content Area", bg="#ecf0f1").pack(expand=True)
root.mainloop()
Common Layout Patterns
| Layout Goal | Pack Configuration |
|---|---|
| Full-width header/footer | pack(side='top', fill='x') |
| Full-height sidebar | pack(side='left', fill='y') |
| Expanding content area | pack(expand=True, fill='both') |
| Centered widget | pack(expand=True) |
| Fixed-size, top-aligned | pack() (defaults) |
Multiple Expanding Widgets
When multiple widgets have expand=True, they share available space equally:
import tkinter as tk
root = tk.Tk()
root.geometry("400x300")
# Three panels sharing vertical space equally
colors = ["#e74c3c", "#3498db", "#2ecc71"]
labels = ["Panel 1", "Panel 2", "Panel 3"]
for color, text in zip(colors, labels):
tk.Label(root, text=text, bg=color, fg="white").pack(
expand=True, fill='both'
)
root.mainloop()
Practical Application: Complete Layout
import tkinter as tk
def create_app():
root = tk.Tk()
root.title("Application Layout")
root.geometry("600x400")
# Top toolbar - fixed height, full width
toolbar = tk.Frame(root, bg="#333", height=40)
toolbar.pack(side='top', fill='x')
toolbar.pack_propagate(False) # Maintain fixed height
tk.Button(toolbar, text="File").pack(side='left', padx=5, pady=5)
tk.Button(toolbar, text="Edit").pack(side='left', padx=5, pady=5)
# Bottom status bar - fixed height, full width
statusbar = tk.Frame(root, bg="#ddd", height=25)
statusbar.pack(side='bottom', fill='x')
statusbar.pack_propagate(False)
tk.Label(statusbar, text="Ready", bg="#ddd").pack(side='left', padx=10)
# Left panel - fixed width, remaining height
left_panel = tk.Frame(root, bg="#f0f0f0", width=150)
left_panel.pack(side='left', fill='y')
left_panel.pack_propagate(False)
tk.Label(left_panel, text="Navigation", bg="#f0f0f0").pack(pady=10)
# Main content - expands to fill remaining space
main_area = tk.Frame(root, bg="white")
main_area.pack(side='left', expand=True, fill='both')
tk.Label(main_area, text="Main Content", bg="white",
font=("Arial", 16)).pack(expand=True)
return root
app = create_app()
app.mainloop()
Never use pack() and grid() for widgets sharing the same parent container. The managers conflict, causing the application to freeze. Choose one manager per container.
Use pack_propagate(False) on frames when you want them to maintain a fixed size regardless of their contents. This is essential for toolbars, sidebars, and status bars.
By mastering fill and expand, you can create Tkinter interfaces that respond naturally to window resizing, providing a professional user experience.