Skip to content

Text

Text is Tkinter's multi-line text editor widget (tk.Text).

It supports rich behavior that typical entry widgets don't:

  • multiple lines with wrapping and indentation

  • tags for styling and interaction (links, highlights, code blocks)

  • embedded images and widgets

  • undo/redo, search, marks, and selections

bootstack exposes Text as a first-class widget so you can build editors, logs, and rich text UIs with a consistent theme and a clear set of usage patterns.

Prefer Field-based inputs when possible

For most form input, prefer TextEntry, PasswordEntry, NumericEntry, etc. Use Text when you need multi-line editing or tag-based formatting.


Quick start

Create a text widget, insert content, and read it back:

import bootstack as bs

app = bs.App()

text = bs.Text(app, width=60, height=12, wrap="word")
text.pack(fill="both", expand=True, padx=20, pady=20)

text.insert("end", "Hello from Text\n")
text.insert("end", "This is a multi-line widget.")

print(text.get("1.0", "end-1c"))  # get all text, excluding trailing newline

app.mainloop()

When to use

Use Text when:

  • you need multi-line editing or display

  • you need tags (highlighting, link-like behavior, syntax coloring)

  • you need embedded images/widgets

Consider a different control when...

  • you want the standard "Text + scrollbar" composite with less wiring - prefer ScrolledText

  • input is part of a form and you want label/message/validation and on_input/on_changed - prefer TextEntry (and other Field widgets)


Appearance

Text has a large option surface. These are the ones you'll use most.

Size and wrapping

  • width - number of characters (int)

  • height - number of lines (int)

  • wrap - "none", "char", "word"

bs.Text(app, width=80, height=24, wrap="word")

Width/height are character/line counts

Some type stubs allow "screen units" for height, but Tk's Text widget is documented in terms of characters (width) and lines (height). Prefer integers for portable behavior.

Editing and undo

  • state - "normal" or "disabled"

  • undo - enable undo/redo

  • maxundo - undo stack limit

  • autoseparators - automatically insert undo separators

text = bs.Text(app, undo=True, maxundo=200, autoseparators=True)

Padding and paragraph spacing

  • padx, pady - internal padding

  • spacing1, spacing2, spacing3 - spacing above/between/below lines

bs.Text(app, padx=10, pady=8, spacing1=2, spacing2=2, spacing3=2)

Theme-critical colors

These options matter most for light/dark theme consistency:

  • background, foreground

  • insertbackground (caret)

  • selectbackground, selectforeground

  • inactiveselectbackground

If bootstack applies defaults, you can usually rely on them. If you manually override, set the full set so the control stays readable in both themes.


Examples and patterns

The Text index model

Many Text methods use indices instead of numeric positions.

Common indices:

  • "1.0" - line 1, character 0 (start of content)

  • "end" - end of content (includes the trailing newline)

  • "end-1c" - end minus 1 character (commonly "real end")

  • "insert" - current cursor position

  • "sel.first" / "sel.last" - selection range (when selection exists)

Examples:

# insert at cursor
text.insert("insert", "typed here")

# delete current selection (if any)
if text.tag_ranges("sel"):
    text.delete("sel.first", "sel.last")

# ensure a specific index is visible
text.see("end")

Read-only text (log viewer)

Text doesn't have a "readonly" state like ttk entries. Use state="disabled" and temporarily enable when updating:

def append_line(s: str):
    text.configure(state="normal")
    text.insert("end", s + "\n")
    text.see("end")
    text.configure(state="disabled")

Clear content

text.delete("1.0", "end")

Search and highlight

def highlight(term: str):
    text.tag_remove("hit", "1.0", "end")
    if not term:
        return

    start = "1.0"
    while True:
        pos = text.search(term, start, stopindex="end", nocase=True)
        if not pos:
            break
        end = f"{pos}+{len(term)}c"
        text.tag_add("hit", pos, end)
        start = end

    text.tag_configure("hit", background="#fff3cd")  # example highlight

Tags

Tags are the most powerful feature of Text. They let you style and interact with ranges of text.

Style a range

text.insert("end", "Normal\n")
start = text.index("end-1c linestart")
text.insert("end", "Bold line\n")

text.tag_add("bold", start, "end-1c")
text.tag_configure("bold", font="body[bold]")
def open_link(_):
    print("clicked")

text.insert("end", "Open settings")
start = "1.0"
end = "1.0 lineend"

text.tag_add("link", start, end)
text.tag_configure("link", underline=True)
text.tag_bind("link", "<Button-1>", open_link)
text.tag_bind("link", "<Enter>", lambda e: text.configure(cursor="hand2"))
text.tag_bind("link", "<Leave>", lambda e: text.configure(cursor="xterm"))

Tag names are your API

Use stable tag names like "error", "warning", "link", "code" so your app can update styling globally.

Scrolling

Text uses yscrollcommand / yview to connect a scrollbar.

import bootstack as bs

app = bs.App()

frame = bs.Frame(app)
frame.pack(fill="both", expand=True, padx=20, pady=20)

text = bs.Text(frame, wrap="word")
text.pack(side="left", fill="both", expand=True)

sb = bs.Scrollbar(frame, orient="vertical", command=text.yview)
sb.pack(side="right", fill="y")

text.configure(yscrollcommand=sb.set)

app.mainloop()

Events and change detection

A common "gotcha" is that Text does not emit a simple <<Changed>> event like your Field-based widgets.

Two common approaches:

  • Bind key/mouse events (simple, but noisy)

  • Use the modified flag (edit_modified) and <<Modified>> pattern (more controlled)

Example pattern:

def on_modified(_):
    # Reset the modified flag so the event can fire again
    text.edit_modified(False)
    print("text changed")

text.bind("<<Modified>>", on_modified)
text.edit_modified(False)

Behavior

Performance tips

  • Prefer batch inserts (insert a full chunk) over many tiny inserts.

  • Use tags to style ranges rather than rebuilding the widget contents.

  • For very large logs, consider truncating older content:

MAX_CHARS = 200_000

def trim():
    # Tk < 3.13 returns a tuple from count()
    n = text.count("1.0", "end", "chars")
    chars = n[0] if isinstance(n, (tuple, list)) else int(n)
    if chars > MAX_CHARS:
        text.delete("1.0", "1.0+20000c")

Additional resources

  • ScrolledText - Text with built-in scrolling

  • TextEntry - Field-based single-line input

  • Form - spec-driven form builder (usually uses Field-based inputs)

  • Scrollbar / ScrollView - scrolling primitives

  • Canvas - drawing/virtualization primitive (often used for custom editors)

API reference

  • bootstack.Text