Skip to main content

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 container
  • fill: Determines whether the widget stretches to occupy its allocated parcel
ParameterPurposeValues
expandClaim available spaceTrue or False
fillStretch 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()
Observing the Difference

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 GoalPack Configuration
Full-width header/footerpack(side='top', fill='x')
Full-height sidebarpack(side='left', fill='y')
Expanding content areapack(expand=True, fill='both')
Centered widgetpack(expand=True)
Fixed-size, top-alignedpack() (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()
Don't Mix Geometry Managers

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.

pack_propagate(False)

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.