Python – Tkinter Grid Example

Tkinter’s grid geometry manager is my go-to tool whenever I need to lay out widgets in a Python GUI. After years of reaching for pack first, I keep coming back to grid because it gives me precise control over where everything sits. Let me walk you through how I actually use it in real applications.

The grid manager divides the window into rows and columns, like a table. You assign each widget a row and column number, and grid handles the rest. This makes it dead simple to build forms, dashboards, and any layout where widgets need to line up neatly. Here’s what I found most useful about it.

TLDR

  • grid places widgets by row and column – use row=0, column=0 for top-left, row=1, column=2 for second row, third column
  • columnspan and rowspan let widgets span multiple cells – button.grid(columnspan=2) makes a button take 2 columns
  • sticky controls alignment within a cell – sticky=’w’ aligns left, sticky=’e’ aligns right, sticky=’ew’ stretches horizontally
  • Never mix pack and grid in the same parent widget – it causes layout bugs that are hard to debug

Why I Reach for Grid Over Pack

I used pack for a long time because it felt simpler. But once I started building applications with more than a few widgets, pack started fighting me. The issue is that pack thinks in terms of packing widgets into a cavity, and controlling exactly where something goes requires understanding parent containers, fill options, and side arguments – it gets complicated fast.

Grid is more direct. You tell it where you want something, and it puts it there. A label at row 0, column 0. An entry field at row 0, column 1. A button at row 1, column 0. The layout is a table, and you decide what goes in each cell.

Basic Grid Placement

Here’s the simplest possible example – two labels and two entry fields arranged in a clean form layout.


import tkinter as tk

master = tk.Tk()
master.title("Grid Form")

# Labels in column 0
tk.Label(master, text="First Name:").grid(row=0, column=0, padx=5, pady=5)
tk.Label(master, text="Last Name:").grid(row=1, column=0, padx=5, pady=5)

# Entry widgets in column 1
entry_first = tk.Entry(master)
entry_first.grid(row=0, column=1, padx=5, pady=5)

entry_last = tk.Entry(master)
entry_last.grid(row=1, column=1, padx=5, pady=5)

tk.mainloop()

padx and pady add spacing around each widget. Without it, everything sits tight against the edges and other widgets. I almost always use at least a little padding – it makes the GUI feel less cramped.

Making Widgets Span Multiple Cells

Sometimes you need a widget to take up more than one cell. A button that spans two columns, or an image that spans two rows. That’s what columnspan and rowspan are for.


import tkinter as tk

master = tk.Tk()
master.title("Spanning Widgets")

# A label in column 0-1 (spans 2 columns)
title = tk.Label(master, text="Registration Form", font=("Arial", 16))
title.grid(row=0, column=0, columnspan=2, pady=10)

# Name fields on row 1
tk.Label(master, text="Name:").grid(row=1, column=0, sticky='e', padx=5)
tk.Entry(master).grid(row=1, column=1, padx=5)

# A submit button that spans both columns
submit_btn = tk.Button(master, text="Submit")
submit_btn.grid(row=2, column=0, columnspan=2, pady=10)

tk.mainloop()

I set sticky=’e’ on the name label – that pins it to the east (right) side of its cell, so the label and entry field end up properly aligned even if the window resizes.

Controlling Alignment with sticky

sticky is one of those arguments I overlooked for too long. It controls which edges of the cell a widget sticks to. By default, widgets center in their cells. But you can stretch them:

  • sticky=’w’ – align left (west)
  • sticky=’e’ – align right (east)
  • sticky=’n’ – align top (north)
  • sticky=’s’ – align bottom (south)
  • sticky=’ew’ – stretch horizontally to fill the cell
  • sticky=’ns’ – stretch vertically
  • sticky=’nsew’ – fill the entire cell

For entry fields that should stretch when the window grows, I use sticky=’ew’ so they expand left-to-right.


import tkinter as tk

master = tk.Tk()
master.title("Sticky Alignment")

# Entry stretches horizontally
entry = tk.Entry(master)
entry.grid(row=0, column=0, sticky='ew', padx=5, pady=5)

# Button stays small on the right
button = tk.Button(master, text="Go")
button.grid(row=0, column=1, padx=5, pady=5)

# Make column 0 stretch
master.columnconfigure(0, weight=1)

tk.mainloop()

master.columnconfigure(0, weight=1) tells column 0 to expand when the window grows. Without it, sticky=’ew’ has no extra space to stretch into.

Building a Login Form with Grid

Let me show you a complete login form. This is the kind of layout I build most often – labels on the left, entry fields on the right, buttons at the bottom.


import tkinter as tk
from tkinter import messagebox

def on_login():
    username = entry_user.get()
    password = entry_pass.get()
    if username and password:
        messagebox.showinfo("Login", f"Welcome, {username}!")
    else:
        messagebox.showwarning("Error", "All fields required")

master = tk.Tk()
master.title("Login")
master.geometry("300x150")

# Username row
tk.Label(master, text="Username:").grid(row=0, column=0, sticky='e', padx=5, pady=5)
entry_user = tk.Entry(master)
entry_user.grid(row=0, column=1, sticky='ew', padx=5, pady=5)

# Password row
tk.Label(master, text="Password:").grid(row=1, column=0, sticky='e', padx=5, pady=5)
entry_pass = tk.Entry(master, show="*")
entry_pass.grid(row=1, column=1, sticky='ew', padx=5, pady=5)

# Buttons
tk.Button(master, text="Login", command=on_login).grid(row=2, column=0, padx=5, pady=10)
tk.Button(master, text="Cancel", command=master.quit).grid(row=2, column=1, sticky='e', padx=5, pady=10)

# Allow column 1 to stretch
master.columnconfigure(1, weight=1)

tk.mainloop()

The layout has three rows. Labels are pinned to the east with sticky=’e’ so they sit right next to their entry fields. The entry fields stretch with sticky=’ew’. The cancel button stays right-aligned within its cell with sticky=’e’.

Common Mistakes I Made

Mixing pack and grid in the same parent is the mistake I see most often, and it took me a while to fully internalize why it breaks. pack and grid both try to manage widget geometry, and they step on each other’s toes. The result is that widgets end up in unexpected positions or get resized incorrectly.

My rule: pick one geometry manager per parent widget. If you’re using grid for the main window, use grid for all child frames too. If you need something pack handles better (like toolbar buttons that should just stack in order), use pack for the whole frame – but then don’t mix it with grid inside that frame.

Another mistake: forgetting to call mainloop() or calling it twice. Without mainloop(), the window never appears. Calling it twice is less obvious – the program seems to run but the window is unresponsive. If your GUI freezes, check that mainloop() is called exactly once.

FAQ

Q: How is grid different from pack?

pack places widgets along a side of a container, filling available space in order. grid places widgets in a table by row and column, which gives more precise control over 2D layouts. For anything that needs to line up in rows and columns, grid is usually the better choice.

Q: What does columnspan do?

columnspan tells a widget to span multiple columns. A widget at row=0, column=0 with columnspan=2 occupies both column 0 and column 1 in that row. This is useful for labels or buttons that need to be wider than a single column.

Q: What does rowspan do?

rowspan works the same way as columnspan but for rows. A widget with rowspan=2 occupies two consecutive rows. This is useful for vertical elements like scrollable text areas or tall images that need more vertical space.

Q: How do I make widgets resize with the window?

Use sticky to make widgets stick to cell edges, and columnconfigure or rowconfigure with weight to make rows and columns expand proportionally. For example, master.columnconfigure(0, weight=1) makes column 0 take extra space when the window grows.

Q: Can widgets overlap in grid?

Grid will not prevent overlapping by default. If two widgets occupy the same cells, they overlap. This is rarely useful – if you want overlapping widgets, a Canvas with explicit positioning is a better approach.

I find grid covers most of what I need for Python GUIs. Once you’re comfortable with row and column placement, sticky alignment, and spanning, you can build surprisingly complex layouts without reaching for anything more powerful.

Vijaykrishna Ram
Vijaykrishna Ram
Articles: 99