Text Wrapping
By default, Text elements render their content on a single line.
When the content is wider than the available space, it overflows rather than wrapping.
Use the text_wrap_* style utilities to control how long text is broken across lines.
Wrap Modes
Counterweight supports four wrap modes, illustrated below with the same paragraph of text:
import asyncio
from counterweight.app import app
from counterweight.components import component
from counterweight.controls import Quit, Screenshot
from counterweight.elements import Div, Text
from counterweight.styles.styles import Style, TextWrap
from counterweight.styles.utilities import *
SAMPLE = "It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness."
WRAP_STYLE: dict[TextWrap, Style] = {
"none": text_wrap_none,
"stable": text_wrap_stable,
"pretty": text_wrap_pretty,
"balance": text_wrap_balance,
}
@component
def wrap_pane(mode: TextWrap) -> Div:
return Div(
style=grow(1) | min_width(0) | col | align_children_stretch | border_light | pad_x(1),
children=[
Text(
content=f" {mode} ",
style=position_absolute | inset_top(-1) | inset_left(1),
),
Text(
content=SAMPLE,
style=grow(1) | WRAP_STYLE[mode],
),
],
)
@component
def root() -> Div:
return Div(
style=col | full | align_children_stretch,
children=[
Div(
style=row | grow(1) | align_children_stretch,
children=[wrap_pane(mode) for mode in WRAP_STYLE],
),
],
)
text_wrap_none (default)
No wrapping. The text renders as a single line regardless of the available width. Content that exceeds the container width is clipped.
text_wrap_stable
Greedy line-breaking — fills each line as much as possible before starting the next. This is the fastest algorithm and produces consistent, deterministic output. Use it when performance matters or when you want predictable line breaks.
text_wrap_balance
Attempts to make all lines roughly equal in length by binary-searching for the narrowest target width that doesn't increase the total line count. Useful for headings and short paragraphs where visual balance is more important than filling lines to the edge.
text_wrap_pretty
Dynamic-programming line-breaking (Knuth–Plass style) that minimises the sum of squared slack on non-final lines. This produces typographically "nicer" output than greedy wrapping — shorter lines near the end of a paragraph are avoided — at the cost of slightly higher CPU usage for very long texts.
Required Layout Setup
Text wrapping requires the Text element to receive a definite width from the layout
engine.
This happens when its containing column has align_children_stretch
and itself has a constrained width.
Without a definite width the measure callback receives an unbounded available width and
returns the text's natural (single-line) size, so no wrapping occurs.
# ✗ wrapping won't happen — Text has no definite width
Div(
style=col,
children=[Text(content="...", style=text_wrap_stable)],
)
# ✓ Text receives a definite width and wrapping works
Div(
style=col | align_children_stretch,
children=[Text(content="...", style=text_wrap_stable)],
)
See Text wrapping doesn't happen in the Common Layout Problems guide for a fuller explanation and the typical root/pane setup.
Long Words and Hyphenation
Words longer than the available width are broken with a hyphen (-) inserted at the
break point.
This applies to all three wrapping modes.
Multi-paragraph Text
Newline characters (\n) in the content string are treated as paragraph separators.
Each paragraph is wrapped independently, so explicit line breaks in your content are
always preserved.