2025, Dec 28 13:00

How to Auto-Size a Tkinter Text Widget to Fit Content: Width/Height Calculation, Wrapping, Max Width

Learn how to auto-size a Tkinter Text widget to fit its content: compute width/height, handle word wrapping and max width, and style with tags using Python.

Auto-sizing a Tkinter Text widget sounds straightforward until you try it. You insert a short string and end up with a generously sized text editor instead of a compact label-like box. When you need rich text features such as mixed styling via tags (bold, italic, colors) and still want the widget to fit its content, you need to calculate dimensions explicitly and adjust them at runtime.

Reproducing the issue

The behavior is easy to observe. The following example creates a Text that’s much larger than the string it contains.

import tkinter as tk

app = tk.Tk()
txt_box = tk.Text(master=app)
txt_box.insert(index="1.0", chars="short string")

txt_box.tag_configure(tagName="center", justify="center")
txt_box.tag_add("center", "1.0", tk.END)

txt_box.grid(row=0, column=0, sticky=(tk.N, tk.E, tk.S, tk.W))

app.mainloop()

What’s going on

The widget isn’t shrinking to the text. Manually setting width and height or experimenting with grid_propagate(False) does not produce the desired “shrink to fit and expand with content” behavior. If you need the Text to size itself to the text it holds, you must derive the dimensions from the content and update the widget accordingly. When word wrapping and maximum width are involved, you also need to consider pixel-based measurement and line wrapping.

Solution: size to content (no wrapping)

When wrapping is disabled, the content’s size in characters and lines can drive the Text dimensions directly. The idea is simple: count lines, find the longest line length in characters, and set the width and height to those values.

import tkinter as tk
from tkinter import font as tkfont

def fit_to_content(widget):
    body = widget.get("1.0", "end-1c")
    rows = body.split("\n")
    fnt = tkfont.Font(font=widget['font'])  # font object available if needed
    max_line_length = max(len(r) for r in rows)
    width_in_chars = max_line_length
    height_in_lines = len(rows)
    widget.config(width=width_in_chars, height=height_in_lines)

win = tk.Tk()
box = tk.Text(master=win, wrap="none")
box.insert(index="1.0", chars="short string")
box.tag_configure("center", justify="center")
box.tag_add("center", "1.0", tk.END)
box.grid(row=0, column=0)
fit_to_content(box)
win.mainloop()

Formatting with fixed size

If you only need multiple styles (bold, italic) and are fine with explicit dimensions, tags make it straightforward.

import tkinter as tk

root = tk.Tk()

text_area = tk.Text(root, width=40, height=5, wrap=tk.WORD)
text_area.pack()

text_area.insert(tk.END, "This is normal text, ")
text_area.insert(tk.END, "this is bold text\n", "b")
text_area.insert(tk.END, "This is italic text", "i")

text_area.tag_configure("b", font=('Arial', 10, 'bold'))
text_area.tag_configure("i", font=('Arial', 10, 'italic'))

root.mainloop()

Fixed width with auto height

When you want the Text to wrap at a certain character width but grow vertically to fit all lines, compute the number of lines after insertion and set the height accordingly.

import tkinter as tk

def autosize_height(widget):
    widget.update_idletasks()
    rows = int(widget.index('end-1c').split('.')[0])
    widget.config(height=rows)

app = tk.Tk()

field = tk.Text(app, wrap=tk.WORD, width=30, height=1)
field.pack()

field.insert(tk.END, "Normal text, ")
field.insert(tk.END, "Bold text", "strong")
field.insert(tk.END, "\nItalic text", "em")
field.insert(tk.END, "\nRed text", "red")

field.tag_config("strong", font=('Arial', 10, 'bold'))
field.tag_config("em", font=('Arial', 10, 'italic'))
field.tag_config("red", foreground='red')

autosize_height(field)

app.mainloop()

Alternative: composing with Message widgets

For simple cases where you want automatic wrapping by pixel width without complex inline styling, separate segments can be placed in multiple Message widgets and laid out as needed.

import tkinter as tk

app = tk.Tk()

holder = tk.Frame(app)
holder.pack()

tk.Message(holder, text="Normal text, ", width=100).pack(side=tk.LEFT)
tk.Message(holder, text="Bold text", width=100, font=('Arial', 10, 'bold')).pack(side=tk.LEFT)

app.mainloop()

Auto width with a maximum width and padding

If you want the window to grow with short strings but cap the width for longer content, measure text in pixels, estimate the number of display lines when wrapping is enabled, and set both width and height accordingly. This respects a max_width limit while preserving content visibility via wrapping.

import tkinter as tk
from tkinter.font import Font

def adjust_textbox(widget, max_width=300):
    widget.update_idletasks()

    fnt = Font(root, font=widget.cget("font"))
    char_px = fnt.measure('0')
    limit_px = max_width

    total_lines = 0
    for row in widget.get("1.0", "end-1c").split('\n'):
        row_px = fnt.measure(row)
        if widget.cget("wrap") in ("word", "char") and row_px > limit_px:
            total_lines += max(1, row_px // limit_px + 1)
        else:
            total_lines += 1

    widget.config(height=total_lines)

    max_row_px = max(fnt.measure(row) for row in widget.get("1.0", "end-1c").split('\n'))
    cols = max(1, min(max_width, max_row_px + 10) // char_px)
    widget.config(width=cols)

root = tk.Tk()
root.title("Dynamic Text Widget Size")

pane = tk.Text(
    root,
    wrap=tk.WORD,
    width=1,
    height=1,
    padx=10,
    pady=10
)
pane.pack(padx=10, pady=10)

segments = [
    ("Normal text, ", ""),
    ("Bold text", "b"),
    ("\nItalic text", "i"),
    ("\nRed text with some longer content that might wrap", "r"),
    ("\n", ""),
    ("This is a very long line that should definitely wrap because it exceeds the maximum width we want to allow for our text widget", "bg")
]

for text, tag in segments:
    pane.insert(tk.END, text, tag)

pane.tag_config("b", font=('Arial', 10, 'bold'))
pane.tag_config("i", font=('Arial', 10, 'italic'))
pane.tag_config("r", foreground='red')
pane.tag_config("bg", background='#f0f0f0')

adjust_textbox(pane)
root.mainloop()

Why this matters

When text-heavy UIs must look clean and intentional, oversized widgets are distracting. If you need inline formatting that Label doesn’t provide and still want compact windows, controlling Text dimensions based on content lets you match the UI to the message. For long strings, enforcing a maximum width with word wrapping keeps the layout readable and prevents runaway growth.

Takeaways

Use Text when you need rich formatting via tags. If you want it to fit the content, compute width and height from the current text. For fixed-width scenarios, adjust height to the actual number of lines. If you must respect a maximum width, measure text in pixels, let wrapping do its job, and set the height to the resulting display lines. When styling is minimal, composing separate Message widgets is also an option.